how to use try/catch in streamBuilder? - flutter

I want to do sign-in page and when I use streamBuilder<user?> and want to cover all cases (if the account exists or not) the compiler notes me there is an error inside the builder and you have to make it Async. when I tried to make it like what the compiler say also show another compilation error
my code is:
ElevatedButton(
onPressed: logIn,
child: StreamBuilder<User?>(
stream: FirebaseAuth.instance.authStateChanges(),
builder: (context, snapshot) {
try {
print('inside bulder method');
if (snapshot.hasData) {
print('have account');
return home();
}
} catch (e) {
print("dont have acc");
return login();
}
//checkIn(context, snapshot);
},
),
)
I try to make it as a method but also have the same error
I want to solve it to display a message to the user (there is something error)

You will get error from snapshot.hasError.
ElevatedButton(
onPressed: logIn,
child: StreamBuilder<User?>(
stream: FirebaseAuth.instance.authStateChanges(),
builder: (context, snapshot) {
if (snapshot.hasError) {
} else if (snapshot.connectionState ==
ConnectionState.waiting) {
} else if (snapshot.hasData) {
}else {
}
},
),
)
More on flutter.dev

Related

returning a FutureBuilder as the result of a FutureBuilder Flutter

I'm creating an add friend page in Flutter/Firebase. I used a StreamBuilder to see the changes of the username input. Then I used a FutureBuilder as the result of the StreamBuilder to get my List of User. To verify that the users aren't already in thisUser's friend list, I'm using an other FutureBuilder as the result of the first one. And I still do the same for the friend requests.
It works but I'm pretty sure that not the right way. Could someone explain me how to do that more properly?
StreamBuilder(
stream: controller.usernameStream.stream,
builder: (context, snapshot) {
if (snapshot.hasData) {
String usernameSearched = snapshot.data!;
return FutureBuilder(
future: _userRepo.getUsersStartsByUsername(usernameSearched),
builder: (context, snapshotUsers){
if(snapshotUsers.connectionState == ConnectionState.done){
if (snapshotUsers.hasData){
return controller.futureBuilderFriends(thisFireUser, snapshotUsers);
} else {
return Padding(
padding: const EdgeInsets.all(8.0),
child: Center(child: Text(AppLocalizations.of(context)!.noUsers)),
);
}
}
return Container();
},
);
}
return Container();
},
)
FutureBuilder<List<String>> futureBuilderFriends(User? thisFireUser, AsyncSnapshot<List<U.User>> snapshotUsers) {
return FutureBuilder(
future: _userRepo.getFriendsId(thisFireUser!.uid),
builder: (BuildContext context, AsyncSnapshot<List<String>> snapshotFriends) {
if (snapshotFriends.connectionState == ConnectionState.done) {
if(snapshotFriends.hasData){
return futureBuilderMyFriendRequests(thisFireUser, snapshotFriends.data!, snapshotUsers);
} else {
return futureBuilderMyFriendRequests(thisFireUser, [], snapshotUsers);
}
} else {
return Container();
}
},
);
}
FutureBuilder<List<String>> futureBuilderMyFriendRequests(User thisFireUser, List<String> friends, AsyncSnapshot<List<U.User>> snapshotUsers) {
return FutureBuilder(
future: _userRepo.getFriendRequestsId(thisFireUser.uid),
builder: (BuildContext context, AsyncSnapshot<List<String>> snapshotFriendRequests) {
if (snapshotFriendRequests.connectionState == ConnectionState.done) {
if(snapshotFriendRequests.hasData){
return buildAddFriends(context, snapshotUsers.data!, thisFireUser, friends, snapshotFriendRequests.data!);
} else {
return buildAddFriends(context, snapshotUsers.data!, thisFireUser, friends, []);
}
} else {
return Container();
}
},
);
}
Looks like you are on vanilla architecture. Please make use of bloc or Riverpod and use the transfrom functions and emit the states accordingly

Cannot share a file as it need to wait a future

Share() must display a modal in order to let the user wait in front of a circular progress indicator while I am loading the video file URL.
My code is as below, but I am puzzled about how to architecture: I need to trigger the sharing only once the snapshot.hasData.
How can that be done?
Btw, I use share_plus
Future<void> share(BuildContext context) async {
showModalBottomSheet(
context: context,
builder: (context) {
return FutureBuilder(
future: Video.videoUrl(videoUrl!),
builder: (context, snapshot) {
final file = XFile(snapshot.data!);
Share.shareXFiles([file],
text: "Don't miss this out! Only on Shokaze");
return SizedBox(
height: 200,
child: Center(
child: !snapshot.hasData
? Column(children: [
Text("Preparing sharing…"),
const CircularProgressIndicator(),
])
: Text("Sharing…")));
});
});
}
You should refactor the FutureBuilder using if/else conditions:
if (snapshot.hasData) {
Share.share(snapshot.data!);
return Text(snapshot.data.toString());
} else if (snapshot.hasError) {
return Text(snapshot.error.toString());
} else {
return Scaffold(body: Center(child: CircularProgressIndicator()));
}
Future<void> share(BuildContext context) async {
showModalBottomSheet(
context: context,
builder: (context) {
return FutureBuilder(
future: myFuture(),
builder: (BuildContext context, AsyncSnapshot<String> snapshot) {
if (snapshot.hasData) {
Share.share(snapshot.data!);
return Text(snapshot.data.toString());
} else if (snapshot.hasError) {
return Text(snapshot.error.toString());
} else {
return Scaffold(body: Center(child: CircularProgressIndicator()));
}
},
);
},
);
}

Flutter - Code in StreamBuilder doesn't get executed

I am trying to debug the following method, somehow the none of the breakpoints get hit. The one in the catch block also doesn't get hit. I fail to understand what is happening.
_getWorkout(workoutId) async {
try {
StreamBuilder<QuerySnapshot>(
stream: await FirebaseFirestore.instance
.collection("workouts")
.doc(workoutId)
.collection("exercises")
.snapshots(),
builder:
(BuildContext context, AsyncSnapshot<QuerySnapshot> snapshot) {
//doesn't go here---
print('SNAPSHOT DATA = ' + snapshot.data.toString());
if (!snapshot.hasData) {
//doesn't go here---
return const Text("There are no exercises");
}
//doesn't go here---
return DataTable( ...
],
rows: _getExercises(snapshot),
);
});
} on Exception catch (_, e) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(e.toString()),
duration: Duration(milliseconds: 1000),
),
);
}
}
StreamBuilder is a Widget that can convert a stream of user defined objects, to widgets.
You Should include inside Widget build as a widget
Widget build(BuildContext context) {
return Scaffold(
body: Container(
child:StreamBuilder<QuerySnapshot>(
stream: FirebaseFirestore.instance
.collection("workouts")
.doc(workoutId)
.collection("exercises")
.snapshots(),
builder:
(BuildContext context, AsyncSnapshot<QuerySnapshot> snapshot) {
//doesn't go here---
print('SNAPSHOT DATA = ' + snapshot.data.toString());
if (!snapshot.hasData) {
//doesn't go here---
return const Text("There are no exercises");
}
//doesn't go here---
return DataTable();
}),
),
);
}
You need to return the StreamBuilder from _getWorkout, which you aren't. You are effectively returning null, which will cause flutter to not execute the StreamBuilder at all.
Always setting return types on your methods will help in avoiding oversights like that one.

Close loading dialog in FutureBuilder

I am using Futurebuilder in flutter and having issue while closing the showDialog
Widget build(BuildContext context) {
return Scaffold(
appBar: PreferredSize(
preferredSize: Size( 50.0),
body: FutureBuilder(
future: widget.getAutoCompleteData(),
builder: (context, snapshot) {
if (snapshot.hasData) {
Navigator.of(context).pop();
} else if (snapshot.hasError) {
} else {
LoadingDialog.showLoadingDialog(context, _scaffoldKey);
}
return Center(
child: Container(child: Text("sds"),),
);
}));
}
Getting below error when screen loads
package:flutter/src/widgets/navigator.dart': Failed assertion: line 5013 pos 12: '!_debugLocked': is not true
Change this
FutureBuilder(
future: widget.getAutoCompleteData(),
builder: (context, snapshot) {
if (snapshot.hasData) {
Navigator.of(context).pop();
} else if (snapshot.hasError) { //here this is empty
} else {//remove else statement
LoadingDialog.showLoadingDialog(context, _scaffoldKey);
}
return Center(
child: Container(child: Text("sds"),),
);
})
To This
FutureBuilder<List<Post>>(
future: _dataFetcher.getPosts(),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return Center(
child: CircularProgressIndicator(),
);
}
if (snapshot.hasError) {
return LoadingDialog.showLoadingDialog(context, _scaffoldKey);
}
return Center(child: Text('${snapshot.data![0].title}'));
},
)
It may be caused by the re-entrant of the Navigator (you can check the answer here: Error thrown on navigator pop until : “!_debugLocked': is not true.”
)
Or, maybe you don't want to use FutureBuilder. The FutureBuilder is meant to stay in the same widget/page and show different screens when future data is not ready. If you want to push a loading dialog and close it when data is ready, you can just simply use a Future function
Future pressTheButton(context) async {
LoadingDialog.showLoadingDialog(context, _scaffoldKey); // showDialog here
final data = await getAutoCompleteData(); // await the data
Navigator.of(context).pop(); // pop the loading dialog
// return your data or error
// or rebuild the current widget with the data
}

How to tell a StreamBuilder that I'm retrying to load data after an error

I have a stream builder for loading a list of video posts. Whenever an error occurs, I add an error to the sink and the StreamBuilder displays an error message with a "Try Again" button. My issue is that when I press the button, Nothing (visually) happens but I want to replace the error message with a spinningLoader while it's retrying.
How would I achieve this?
I was thinking of adding a boolean to the sink and check if snapshot.data is bool and return the spinningLoader if it is, but this seems counter-intuitive.
#override
Widget build(BuildContext context) {
return SafeArea(
child: Container(
color: backgroundColor,
child: StreamBuilder(
stream: _streamController.stream,
builder: (context, snapshot) {
if (snapshot.hasData) {
return buildRelatedList(snapshot.data);
} else if (snapshot.hasError) {
return retryButton();
} else {
return _spinningLoader;
}
},
),
),
);
}
_fetchVideos() async {
List videos = await RelatedVideos.fetchVideos(
id: widget.id);
if (videos.isEmpty) {
_streamController.sink.addError('error loading');
} else {
_streamController.sink.add(videos);
}
}
You can use ConnectionState:
builder: (BuildContext context, AsyncSnapshot<int> snapshot) {
if (snapshot.hasError)
return Text('Error: ${snapshot.error}');
switch (snapshot.connectionState) {
case ConnectionState.none: return Text('Select lot');
case ConnectionState.waiting: return Text('Awaiting bids...');
case ConnectionState.active: return Text('\$${snapshot.data}');
case ConnectionState.done: return Text('\$${snapshot.data} (closed)');
}
return null; // unreachable
},
)
That will return the state of connection to an asynchronous computation. Also you can change the Text widget to another widget, for example you can use CircularProgressIndicator();
https://api.flutter.dev/flutter/widgets/ConnectionState-class.html
https://api.flutter.dev/flutter/widgets/StreamBuilder-class.html
https://api.flutter.dev/flutter/widgets/StreamBuilder-class.html