How to retrun an array to the list view flutter - flutter

I have a function working on query an array inside subcollcetion i'ts working good when click it test button its showing me data fine but i need to return the data to the list view How can i do this
function get data
Future < List > getsubcollcation() async {
List Rav;
final firestoreInstance = Firestore.instance;
firestoreInstance.collection("Institute").document(widget.id_document).collection("Ravs").where('Rav name', isEqualTo: 'English').snapshots().listen((snapshot) {
Rav = snapshot.documents.toList();
});
return Rav;
}

So you're not returning a List Widget but a Future Widget. You have to put the Listview into a FutureBuilder widget. Pass the function that returns the Future as it's 'future' parameter. Then write a builder function,
that first checks if the Future has data yet ( if (Future.Connectionstate == Connectionstate.done )),
then you can return a ListView where you put the Future.data as it's 'children' param.

Related

Change a dropdown's items when another dropdown's value is chosen in flutter (UI wont update)

I have two drop downs, and I want to do things when they get values selected. One of those is to change the second buttondrop items based on what's selected in the first dropdown.
For example:
Dropdown1 is a list of car manufactuers
Dropdown2 is a list of their models
Dropdown1 selects mercedes
Dropdown2 gets "E Class, S Class" etc
Dropdown1 selects lexus
Dropdown2 gets "ES, LS", etc
(Eventually the second drop down will update a listview as well, but haven't gotten to that yet.)
Data wise, it works, I update the list. The problem is the UI won't update unless I do a hot reload
Currently I am just having the dropdowns fetch their data and using Future builders
Future? data1;
Future? data2;
void initState(){
super.initState();
data1 = _data1AsyncMethod();
data2 = _data2AsyncMethod();
}
_data2AsyncMethod([int? item1_id]) async{
if(item1_id == null){
item2Classes = await DefaultItems().getAllItem2Classes();
listOfItem2ClassNames = DefaultItems().returnListOfItemClassNames(item2Classes);
}
else{
// The methods below calls the DefaultItems methods which have Futures all in them.
// The getAllItems calls a network file with GET methods of future type to get data and decodes them, etc.
// They build a list of the object type, ex List<Item2>
item2Classes = await DefaultItems().getAllItem2Classes(item1_id);
listOfItem2ClassNames = DefaultItems().returnListOfItemClassNames(item2Classes);
}
}
I have this Future Builder nested in some containers and paddings
FutureBuilder{
future: data2,
builder: (context, snapshot){
if(snapshot.connectionState != done...)
// return a circle progress indictator here
else{
return CustomDropDown{
hintText: 'example hint'
dropDownType: 'name'
dropDownList: listOfItem2ClassNames
dropDownCallback: whichDropDown,
}
The onChanged in CustomDropDown passes the dropDownType and the dropDownValue
The callback
whichDropDown(String dropDownType, String dropDownValue){
if(dropDownType == 'item1'){
//so if the first dropdown was used
// some code to get item_1's id and I call the data2 method
_data2AsyncMethod(item1_id);
}
Again the data updates (listOfItem2ClassNames) BUT the UI won't update unless I hot reload. I've even called just setState without any inputs to refresh but doesn't work
So how do I get the UI to update with the data, and is my solution too convoluted in the first place? How should I solve? StreamBuilders? I was having trouble using them.
Thanks
If you do a setState in the whichDropDown function, it will rebuild the UI. Although I'm not exactly sure what you want, your question is really ambiguous.
whichDropDown(String dropDownType, String dropDownValue){
if(dropDownType == 'item1'){
//so if the first dropdown was used
// some code to get item_1's id and I call the data2 method
_data2AsyncMethod(item1_id).then((_) {
setState(() {});
});
}
}
I notice a couple things:
nothing is causing the state to update, which is what causes a rebuild. Usually this is done explicitly with a call to setState()
in whichDropdown(), you call _data2AsyncMethod(item1_id), but that is returning a new Future, not updating data2, which means your FutureBuilder has no reason to update. Future's only go from un-completed to completed once, so once the Future in the FutureBuilder has been completed, there's no reason the widget will update again.
You may want to think about redesigning this widget a bit, perhaps rather than relying on FutureBuilder, instead call setState to react to the completion of the Futures (which can be done repeatedly, as opposed to how FutureBuilder works)

Flutter GetX, call a controller method after all widgets has been built

I am using GetX and I have following codes.
RxList<Outlets?> listPharmacyOutlets = <Outlets>[].obs;
RxList listOutletCoordinates = [].obs;
#override
void onInit() async{
super.onInit();
await getPharmacyOutlets();
}
Future getPharmacyOutlets() async{
Map params = {
//some parameters
}
var res = await CommonApiProvider.getPharmacyOutlets(params)
var outlets = res.data;
int idx = 0;
listPharmacyOutlets.clear();
for(final outlet in outlets){
listPharmacyOutlets.add(Outlets(
"latitude": outlet.latitude,
"longitude": outlet.longitude,
"pharmacyId": outlet.id,
"outletName": outlet.name,
"address": null
));
//now populating address list to fetch addresses for all outlets
listOutletCoordinates.add({
"index": idx,
"latitude": outlet.latitude,
"longitude": outlet.longitude,
});
idx++;
}
//I cannot call getOutletAddresses() here because view will not be populated unless this completes.
}
Future getOutletAddresses() async {
await Future.forEach(listOutletCoordinates, (item) async {
var result = await CommonApiProvider.getOutletAddresses(item);
//from result update "address" in "listPharmacyOutlets" by the help of index property in item
});
}
Here is my view.
SingleChildScrollView(
physics: AlwaysScrollableScrollPhysics(),
child: Column(
children: List.generate(controller.listPharmacyOutlets.length, (index) {
var item = controller.listPharmacyOutlets[index];
return Container(
child: .......
);
})
),
);
What I want to do is, first method getPharmacyOutlets() call only fetches outlets from Rest api and when list of outlets is completed rendering in list,
call getOutletAddresses() method to fetch address from google's geocoding service against the supplied latitude and longitudes. After address has been fetched, i will
update each row in the rendered list of outlets.
I am fetching addresses and outlets separately because I don't want user to wait for all results to come.
Problem, I am not sure when to call getOutletAddresses() api so that the addresses are fetched only after outlets has been rendered in the view.
I have in my mind that this could be achieved using WidgetsBinding like this.
WidgetsBinding.instance.addPostFrameCallback((_) {
// here call method to fetch address from google service.
});
But I am not sure where to use this because I am using GetX, which means all my pages are stateless
Any help or any other better Idea will help me a lot.
Thanks
The best approach is to use the worker functions provided by getx controller like:
ever - is called every time the Rx variable emits a new value.
everAll - Much like ever , but it takes a List of Rx values Called every time its variable is changed. That's it.
once - is called only the first time the variable has been changed.
For the present situation,
You can use once or everand based on the required condition you can then call your desired function
So the best time to call the getOutletAddresses() function is after listOutletCoordinates is filled .
And in my opinion it's not about the view rendering and then calling a function. Instead its to focus on the data being fetched sequentially. Because the View updation is taken care by Getx Builder or Obx() provided by Getx once the data is properly fetched
If using ever:
ever(listOutletCoordinates,(value){
// Based on the value you can call the getOutletAdresses() function
});
But with once you get more flexibility , Use something like:
once(listOutletCoordinates,(value){
// Based on the value you can call the getOutletAdresses() function
},condition:() => listOutletCoordinates.isNotEmpty);
Alternatively:
You can use listen property to call function based on listening value.
Example:
listOutletCoordinates.listen((val){
// Based on the value you can call the getOutletAdresses() function
});

How to append data into a Future<List<Adds>>

I want to create a on scroll data load. currently I am getting data(ad list) from a api call as a Future<List<Ads>>
in scroll listener function I just want to append next page data list.
there was a example of doing it with a list, I just want to do the same with a Future list
items.addAll(List.generate(42, (index) => 'Inserted $index'));
You could simply do this:
Future<List<Ads>> appendElements(Future<List<Ads>> listFuture, List<Ads> elementsToAdd) async {
final list = await listFuture;
list.addAll(elementsToAdd);
return list;
}
And then call it like this:
appendedListFuture = appendElements(items, yourItemsToAdd);

Flutter Function returns empty list using Firebase

I'm using Flutter web and firebase realtime database. In my databse I have some questions, structured as maps, and I'm trying to display them in my app.
For each question, I create a widget, I add it inside a list and then I append the list to a Column() widget.
The Column() widget is the following (view_question.dart file):
Column(
children: Question().view(),
),
As you can see, I use the method view() from a class I created named Question().
The view() method returns a list of widgets.
Before I show you the .view() method, I need to mention that outside of the Question class I have initialized the following:
List<Widget> widgets = List();
Below you can see the .view() method from the Question class (Question.dart file):
List<Widget> view() {
Database db = database();
DatabaseReference ref = db.ref('questions/');
ref.orderByKey().once("value").then((e) {
DataSnapshot datasnapshot = e.snapshot;
datasnapshot.val().forEach((key, values) {
widgets.add(QuestionWidget(values));
print(widgets);
});
});
print(widgets);
return widgets;
I'm getting my questions' data from my database, then for each question I'm creating a QuestionWidget() widget, where I pass inside it the respective data and I add it to the widgets list. Afterwards, I print() the widgets list, so I can monitor the whole process.
As you also can see, I've added a print(widgets) just before the return statement.
This is the prints' output in my console
From that, what I understand is that the function returns an empty list and then I retrieve my data from the database and add them to the list.
So I'm asking, how can I add my data to the list, and when this process has finished, return the list and append it to the Column() widget? Should I add a delay to the return statement? What am I missing?
Thank you for your time
Data is loaded from Firebase asynchronously. By the time your return widgets runs, the widgets.add(QuestionWidget(values)) hasn't been called yet. If you check the debug output of your app, the print statements you have should show that.
For this reason you should set the widgets in the state once they're loaded. That will then trigger a repaint of the UI, which can pick them up from the state. Alternatively you can use a FutureBuilder to handle this process.
See:
How can I put retrieved data from firebase to a widget in Flutter?
Slow data loading from firebase causing "RangeError (index)"
// return type is like this
Future<List<Widget>> view() {
Database db = database();
DatabaseReference ref = db.ref('questions/');
// return this
return ref.orderByKey().once("value").then((e) {
DataSnapshot datasnapshot = e.snapshot;
datasnapshot.val().forEach((key, values) {
widgets.add(QuestionWidget(values));
});
// return widgets after added all values
return widgets;
});

Function invoke in Flutter to get attributes from Firebase

I want to define and invoke a function in Flutter to get required values from Firebase.
In the below code I have defined getCourseDetails() function and invoking it in the container by passing a parameter to get the value.
The Course_Details collection has many documents with course_id's which has attribute with name (course_name, subtitle). I use these values to build a listview cards in next steps.
I am able to get the values from the function using async await, but for some reason the values keeps on updating and never stops. It kind of goes to loop and keeps on running. I added print statements to check and it keeps on running and printing.
Please let me know what wrong I am doing here or how to define function here to avoid the issue. Thanks
class _CourseProgressListState extends State<CourseProgressList> {
String course_name, subtitle;
getCourseDetails(course_id_pass) async {
DocumentSnapshot document = await Firestore.instance.collection('Course_Details').document(course_id_pass).get();
setState(() {
course_name = document.data['course_name'];
subtitle = document.data['subtitle'];
});
}
#override
Widget build(BuildContext context) {
return Container(
child: StreamBuilder(
stream: Firestore.instance.collection('Enrolling_Courses').where('student_id', isEqualTo: widget.id).snapshots(),
builder: (context, snapshot) {
if (snapshot.data == null) return Text('no data');
return ListView.builder(
itemCount: snapshot.data.documents.length,
itemBuilder: (_, int index) {
var course_details = snapshot.data.documents[index];
getCourseDetails(course_details['course_id']);
Application Flow
On first build of CourseProgressList the Widget State _CourseProgressListState is loaded. This State in the build method uses a StreamBuilder to retrieve all documents from the firestore. As soon as the documents are received the StreamBuilder attempts to build the ListView using the ListViewBuilder.
In the ListViewBuilder you make the async call to getCourseDetails(course_details['course_id']); which when complete populates two attributes String course_name, subtitle;
The problem starts here
Problem
When you call
setState(() {
course_name = document.data['course_name'];
subtitle = document.data['subtitle'];
});
you trigger a Widget rebuild and so the process starts over again to rebuild the entire widget.
NB. refreshing state of a stateful widget will trigger a widget rebuild
NB. Firestore.instance.collection('Enrolling_Courses').where('student_id', isEqualTo: widget.id).snapshots() returns a stream of realtime changes implying that your List will also refresh each time there is a change to this collection
Recommendations
If you do not need to call the setState try not to call the setState.
You could let getCourseDetails(course_id_pass) return a Future/Stream with the values desired and use another FutureBuilder/StreamBuilder in your ListViewBuilder to return each ListViewItem. Your user may appreciate seeing some items instead of waiting for all the course details to be available
Abstract your request to firestore in a repository/provider or another function/class which will do the entire workload, i.e retrieving course ids then subsequently the course details and returning a Future<List<Course>> / Stream<List<Course>> for your main StreamBuilder in this widget (see reference snippet below as a guide and requires testing)
Reference Snippet
Your abstraction could look something like this but this decision is up to you. There are many software design patterns to consider or you could just start by getting it working.
//let's say we had a class Course
class Course {
String courseId;
String courseName;
String subTitle;
Course({this.courseId,this.courseName,this.subTitle})
}
Stream<List<Course>> getStudentCourses(int studentId){
return Firestore.instance
.collection('Enrolling_Courses')
.where('student_id', isEqualTo: studentId)
.snapshots()
//extract documents from snapshot
.map(snapshot => snapshot?.data ?? [])
//we will then request details for each document
.map(documents =>
/*because this is an asynchronous request for several
items which we are all interested in at the same time, we can wrap
this in
a Future.wait and retrieve the results of all as a list
*/
Future.wait(
documents.map(document =>
//making a request to firestore for each document
Firestore.instance
.collection('Course_Details')
.document(document['course_id'])
.get()
/* making a final transformation turning
each document into a Course item which we can easily pass to our
ListBuilder/Widgets
*/
.then(courseItem => Course(
courseId:document['course_id'],
courseName:
courseItem.data['course_name'],
subTitle:
courseItem.data['subtitle']
)
)
)
)
);
}
References/Resources
FutureBuilder - https://api.flutter.dev/flutter/widgets/FutureBuilder-class.html
StreamBuilder - https://api.flutter.dev/flutter/widgets/StreamBuilder-class.html
Future.wait - https://api.flutter.dev/flutter/dart-async/Future/wait.html