Will a snapshot contain an error when throwing an exception? - flutter

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
}
});
}

Related

Nested StreamBuilders Flutter

So I'm currently using this nest of two streams, one to listen for AuthStateChanges, to know if the user is logged in, and another that listens to a firebase document snapshot request, to know if the user has already setup is account or not.
My problem is that the latter StreamBuilder(_userStream) only runs if the firts one runs, meaning that the only way for my _userStream to run is if the user either logs in or logs out(authStateChanges Stream).
This is inconvinient because after the user creates an account(moment where i run Auth().createUserWithPasswordAndEmail()), I need the user to go throw the process of seting up the account, and only after that the user can acess the mainPage. Only in the end of seting up the account theres a button to "Create Account", which changes the "HasSetupAccount" parameter in firebase to true. But because of the nested Streams problem, the app doesn't go to the mainPage until I force update it.
I hope my question is not as confusing as it looks :)
class _WidgetTreeState extends State<WidgetTree> {
#override
//construtor da class?
Widget build(BuildContext context) {
return StreamBuilder(
stream: Auth().authStateChanges,
builder: (context, snapshot) {
if (snapshot.hasData) {
return StreamBuilder(
stream: _userStream(),
builder:
((context, AsyncSnapshot<DocumentSnapshot> userSnapshot) {
if (userSnapshot.connectionState == ConnectionState.waiting) {
return const CircularProgressIndicator();
} else {
Map<String, dynamic> userData =
userSnapshot.data!.data() as Map<String, dynamic>;
print(userSnapshot.data!.data().toString());
if (userData['HasSetupAccount'] == true) {
return MyHomePage();
} else {
return AccountSetup();
}
}
}));
} else {
return LoginPage();
}
},
);
}
Stream<DocumentSnapshot<Map<String, dynamic>>> _userStream() {
return FirebaseFirestore.instance
.collection('Users')
.doc(Auth().currentUser!.uid)
.snapshots();
}
}

Flutter firestore streambuilder with a future

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
}
},
);

How to wait for a regular (non async) code (function) in flutter?

I want to search for a word in a big text file that is local (not calling any HTTP or API).
I'm using it with FutureBuilder because only the opening of the text file is async (the rest isn't)
but the opening itself is very fast.
I want to render CircularProgressIndicator, while searching, but it seems that the moment it finishes opening the file, the CircularProgressIndicator stops, and I have a blank screen for the long searching time.
What can I do to present a loading screen also while doing the regular for loop?
What I have is something like this pseudocode:
Future<Array> searchData() async{
results = [];
someBigTextFile= await getTextFile();
for(row in someBigTextFile){ // this loop takes a lot of time
if(row contains this.query) results.add(row);
}
return results;
}
Widget buildResults(BuildContext context) {
return FutureBuilder(
future: searchData(),
builder: (BuildContext context, AsyncSnapshot<Array> snapshot){
if (snapshot.connectionState != ConnectionState.done) {
print("not done yet");
return CircularProgressIndicator();
} else {
return snapshot.data;
}
}
}
The Future call should not be in your build function. Make the call to your future and use the result in your build function.
Future<Array> result = searchData();
Widget buildResults(BuildContext context) {
return FutureBuilder(
future: result,
builder: (BuildContext context, AsyncSnapshot<Array> snapshot){
if (snapshot.connectionState != ConnectionState.done) {
print("not done yet");
return CircularProgressIndicator();
} else {
return snapshot.data;
}
}
}
Where you make the call for result will depend on the structure of the rest of your code.
Give this a try. With the .then() syntax you can specifically stop sync code from running before a future returns.
Future<Array> searchData() async{
results = [];
await getTextFile().then((someBigTextFile){
for(row in someBigTextFile){ // this loop takes a lot of time
if(row contains this.query) results.add(row);
}
return results;
});
}
You should use isolates to spawn totally new working thread. Something like:
Array searchData( String someBigTextFileContents) {
results = [];
for(row in someBigTextFile){ // this loop takes a lot of time
if(row contains this.query) results.add(row);
}
return results;
}
Future<Array> searchData() async {
someBigTextFileContents= await getTextFile();
return compute( searchData, someBigTextFileContents )
}
Better example on here: https://flutter.dev/docs/cookbook/networking/background-parsing

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

How to make the connection to waiting state by using StreamBuilder in flutter

My requirement is to make that StreamBuilder connection state to waiting.
I'm using publish subject, whenever I want to load data in stream builder I'm just adding data to the sink by calling postStudentsToAssign() method, here this method making an API call which takes some time, in that time I to want make that streamBuilder connection state to waiting
Stream Builder:
StreamBuilder(
stream: studentsBloc.studentsToAssign,
// initialData: [],
builder: (context, snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.waiting:
// While waiting for the data to load, show a loading spinner.
return getLoader();
default:
if (snapshot.hasError)
return Center(child: Text('Error: ${snapshot.error}'));
else
return _getDrawer(snapshot.data);
}
}),
Initializing Observable:
final _assignStudentSetter = PublishSubject<dynamic>();
Observable<List<AssignMilestoneModel>> get studentsToAssign =>
_studentsToAssignFetcher.stream;
Method that add's data to Stream:
postStudentsToAssign(int studyingClass, String milestoneId, String subject,
List studentList) async {
var response = await provider.postAssignedStudents(
studyingClass, milestoneId, subject, studentList);
_assignStudentSetter.sink.add(response);
}
You can send null to the stream, so the snapshot.connectionState changes to active. I don't know why and whether it's official solution, but it works (at least now). I found this accidentally.
I would like the Flutter team to explain how to set snapshot's connectionState. It's not clear from StreamBuilder documentation. It seems you should replace the stream with a new one to have snapshot in waiting state. But it's agains the logic you want to implement.
I checked StreamBuilder source to find out that the AsyncSnapshot.connectionState starts as waiting (after stream is connected), after receiving data changes to active. snapshot.hasData returns true if snapshot.data != null. That's how following code works.
class SearchScreen extends StatelessWidget {
final StreamController<SearchResult> _searchStreamController = StreamController<SearchResult>();
final SearchService _service = SearchService();
void _doSearch(String text) async {
if (text?.isNotEmpty ?? false) {
_searchStreamController.add(null);
_searchService.search(text)
.then((SearchResult result) => _searchStreamController.add(result))
.catchError((e) => _searchStreamController.addError(e));
}
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(children: <Widget>[
SearchBar(
onChanged: (text) => _doSearch(text),
),
StreamBuilder<SearchResult>(
stream: _searchStreamController.stream,
builder: (BuildContext context, AsyncSnapshot<SearchResult> snapshot) {
Widget widget;
if (snapshot.hasData) {
widget = Expanded(
// show search result
);
}
else if (snapshot.hasError) {
widget = Expanded(
// show error
);
}
else if(snapshot.connectionState == ConnectionState.active){
widget = Expanded(
// show loading
);
}
else {
// empty
widget = Container();
}
return widget;
},
),
]),
);
}
}