Flutter firestore streambuilder with a future - flutter

I have a simple question. The reference to my firestore collection is dynamic. In this piece of code, getDocumentReference() gives me a reference to document after checking the user's email.
I use this document reference to get my snapshots.
Future<Stream<QuerySnapshot>> getHabits() async {
DocumentReference document = await getDocumentReference();
var snapshots = document.collection('habits').snapshots();
return snapshots;
}
As you can see, I want to use this Future<Stream<QuerySnapshot>> for a streambuilder. How can I do that? I tried something like this. But it is not taking the future as input to stream
return StreamBuilder(
stream: getHabits(),
);

You can wrap it in a FutureBuilder:
return FutureBuilder<Stream<QuerySnapshot>>(
future: getHabits(),
builder: (context, snapshot) {
if (snapshot.hasData) {
return StreamBuilder(stream: snapshot.data); // Success
} else if (snapshot.hasError) {
return Text('${snapshot.error}'); // Error
} else {
return CircularProgressIndicator(); // Loading
}
},
);

Related

FutureBuilder with lists

This is the future function I want to call:
Future<List<Marker>> getMarkers() async {
List<Marker> markerTemp = [];
List<String> friendsList = [];
QuerySnapshot snapshot = FireStoreUtils.getFriendsList(current.userID);
for (var doc in snapshot.docs) {
friendsList.add(doc.reference.id);
}
for (var friend in friendsList) {
DocumentSnapshot document = await locationRef.doc(friend).get();
MarkerTemp.add(Marker(...))
}
return markerTemp;
}
Now I want it to be called in FutureBuilder widget to save the results in a variable called markerList that is useful for my view. How can I do?
return FutureBuilder<List<Marker>>(
future: getMarkers(),
builder: (context, snapshot) {
if (snapshot.connectionState != ConnectionState.done) {
// async call has not finished
return const Center(child: CircularProgressIndicator());
}
if (snapshot.hasError) {
// getMarkers() throws an exception
return Center(child: Text(snapshot.error.toString()));
}
if (!snapshot.hasData) {
// getMarkers() returns null
return const Center(child: Text("getMarkers() returns null!"));
}
markerList = snapshot.data as List<Marker>; // cast to List<Marker>
return SomeWidget(); // use markerList in this Widget
},
);
Your future builder, when the future finishes in your case, returns a list of markers. Now to use that list, you don't have to store it again, it's already returned and stored in your snapshot in your future builder. You can validate this by printing the length of it:
if(snapshot.hasData) print(snapshot.data.length.toString());

Use Futurebuilder to create a DropdownFormField Flutter?

I'm new to flutter and have a array in firestore. It's structure is:
Now i want to fetch that array, and use that array to build a dropdownFormfield.
My fetch Function is following:
fetchSchoolList() async {
List<dynamic> data = [];
Future<DocumentSnapshot<Object?>> result = schoolList.doc('List').get();
return result;
}
and my FutureBuilder is:
FutureBuilder<DocumentSnapshot<Object?>>(
future: fetchSchoolList(),
builder: (BuildContext context,
AsyncSnapshot<DocumentSnapshot> snapshot) {
if (snapshot.connectionState ==
ConnectionState.active) {
return CircularProgressIndicator();
}
if (snapshot.hasError) {
return Text("Something went wrong");
}
if (snapshot.connectionState == ConnectionState.done) {
print(snapshot);
}
return Text('Loading List');
}),
But with this i'm even unable to get that array to print. I am getting error:
type 'Future' is not a subtype of type 'Future<DocumentSnapshot<Object?>>?'
Please help, i'm stuck here for days.#
There's a couple of things you could do:
First of all change fetchSchoolList() async {...} to something like this:
Future<DocumentSnapshot<List<String>?>>() async {
Future<DocumentSnapshot<List<String>?>> result =schoolList.doc('List').get();
return result;
}
Object isn't exactly a type, also flutter doesn't really know how to store information in with the type <Object>. Also you don't reference the data variable anywhere so it has been omitted.
Next change your FutureBuilder to something like this:
FutureBuilder<DocumentSnapshot<List<String>?>>(
future: fetchSchoolList(),
builder: (context, snapshot) {
if (snapshot.connectionState ==
ConnectionState.active) {
return CircularProgressIndicator();
}
if (snapshot.hasError) {
return Text("Something went wrong");
}
if (snapshot.connectionState == ConnectionState.done) {
print(snapshot);
}
return Text('Loading List');
}),
This will specify what is being returned by the future (Basically the FutureBuilder's type) and because of that, you do not need to specify the snapshot type.

Will a snapshot contain an error when throwing an exception?

This method sends a message and returns Future<bool>. I case when status in not 200 will the snapshot contains error or it will crash the whole app by throwing an exception?
Future<bool> sendMessage(String id, String message) async {
/* sendind logic */
if (r.statusCode == 200) {
return true;
} else {
print('Failed to send message. Status code: ${r.statusCode}');
throw Exception('Failed to send message. Status code: ${r.statusCode}');
}
}
and proceed it this way
FutureBuilder(
future: result,
builder: (context, snapshot) {
if (snapshot.hasData) {
return Center(child: Text('Success'));
} else if (snapshot.hasError) {
return Center(child: Text("${snapshot.error}"));
}
return kLoading;
},
),
In case when method returns Future<void>, how should I check in FutureBuilder
is future completed or not?
AsyncSnapshot is the immutable representation of the results received from the latest interaction with the asynchrounous computation (e.g. server API call, sqlite database call, shreadpref call, etc.)
So if the latest computation resulted in an error (exception) that also this object will have in the snapshot.error field. SO in your case, the code should look like below:
#override
Widget build(BuildContext context) {
return FutureBuilder(
future: result,
builder: (context, snapshot) {
if (snapshot.hasData) {
// Data is avialable. call snapshot.data
}
else if(snapshot.hasError){
// Do error handling
}
else {
// Still Loading. Show progressbar
}
});
}

how can I provide Future<String> to stream that expects String in Dart? [duplicate]

This question already has answers here:
What is a Future and how do I use it?
(6 answers)
Closed 2 years ago.
In my flutter app I'm building a scrollable list filled with data from FireBase. So far my code looks as follows:
class FollowersListState extends State<FollowersUsersList> {
#override
Widget build(BuildContext context) {
return StreamBuilder<QuerySnapshot>(
stream: Firestore.instance.collection('user').where("author", isEqualTo: getUserEmail()).snapshots(),
builder: (context, snapshot) {
if (!snapshot.hasData) return CupertinoActivityIndicator();
return _buildList(context, snapshot.data.docs);
},
);
}
In the code above there is this line:
where("author", isEqualTo: getUserEmail())
getUserEmail() is a method that takes the user's email from local shared preferences:
Future<String> getUserEmail() async {
final prefs = await SharedPreferences.getInstance();
return prefs.getString('loginEmail');
}
but - as you can see - it returns the Future<String> instead of String, so I cannot use it in my implementation in the StreamBuilder<QuerySnapshot>. What is the best way to handle it in that case?
Thanks!
You can't unpack a Future into a synchronous value within the build method. You will need to wrap the StreamBuilder in a FutureBuilder that gets the email ahead of time:
FutureBuilder(
future: getUserEmail(),
builder: (_, emailSnapshot) {
if (!emailSnapshot.hasData) {
return CircularProgressIndicator(); // Or something
}
return StreamBuilder(
stream: Firestore.instance.collection('user').where("author", isEqualTo: emailSnapshot.data).snapshots(),
builder: (context, snapshot) {
if (!snapshot.hasData) return CupertinoActivityIndicator();
return _buildList(context, snapshot.data.docs);
},
),
},
),

is it possible to create a function to use a FutureBuilder, and not have to repeat code?

Basically I use this syntax to use a FutureBuilder:
retur FutureBuilder(
future: future,// http request
builder: (context, AsyncSnapshot<dynamic> snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
Text("success done");
}
if (snapshot.hasData) {
if (snapshot.data["error"] == "111") {
rerurn Text("not access server")
}
if (snapshot.data["data"]["ok"] == false) {
return Text("problem");
}
return Container();
} else {
return Text("Loading");
}
});
Every time I want to make a web request, I have to write all this code again.
I want to optimize this, so I'm thinking of converting the above code into a method where I simply pass a future (http request) parameter and return what my FutureBuilder would return.
I'm trying something like this:
Future generateFutureBuilder(Future<dynamic> future, Widget widget) {
//widget is a Widget to show when it finishes
FutureBuilder(
future: future,// http request
builder: (context, AsyncSnapshot<dynamic> snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
Text("success done");
}
if (snapshot.hasData) {
if (snapshot.data["error"] == "111") {
}
if (snapshot.data["data"]["ok"] == false) {
return Text("problem");
}
return widget;
} else {
return Text("Loading");
}
});
}
generateFutureBuilder(http.get(myurl), Container(child:Text("finished")))
but it doesn't behave like it normally does.
What I can do?
Although using a method to abstract widgets is OK, it typically is better to create a class that extends StatelessWidget. To fix your current method, you need to make it return FutureBuilder, not Future, and actually add a return statement. Another good change would be changing the use of dynamics to generics for better type safety.
But another plausible solution that will give you a little more control when building your widget is to not abstract away the FutureBuilder itself, but just the snapshot it gives you. Example:
/// Returns a widget to show if needed (either error or loading), or null if success
Widget getFutureSnapshotWidget(AsyncSnapshot snapshot) {
// if has data that is ok:
// return null;
// else:
// return either an error as a Text or a CircularProgressIndicator
}
Then you can call the following in your builder of FutureBuilder:
final widgetToShow = getFutureSnapshotWidget(snapshot);
if (widgetToShow != null) {
return widgetToShow;
}
// Show regular widget here