Flutter - Code in StreamBuilder doesn't get executed - flutter

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.

Related

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

Modify Future Builder inside Stream Builder to avoid widget flickering

I am using a FutureBuilder inside a StreamBuilder that updates the UI every time a document is added to the activity collection, to get some aditional data from Firestore. The problem is that the FutureBuilder returns a SizedBox widget while the ConnectionState is waiting causing the all the cards to dissapear for a second. I would like to avoid this flickering since it causes a bad ui experience for users.
Is there a way to query the required user data in the activity stream so it all returns at once that way I can remove the FutureBuilder?
If not ... what would be a solution for this?
activityStream() {
return FirebaseFirestore.instance
.collection('activity')
.orderBy('timestamp', descending: true)
.limit(55)
.snapshots();
}
if (snapshot.connectionState == ConnectionState.waiting) {
return const SizedBox(
height: 65.0,
);
}
StreamBuilder<QuerySnapshot>(
stream: activityStream(),
builder: (BuildContext context, AsyncSnapshot<QuerySnapshot> snapshot) {
if (snapshot.hasError) {
return Text('Error: ${snapshot.error}');
}
switch (snapshot.connectionState) {
case ConnectionState.waiting:
return const Center(child: CircularProgressIndicator());
default:
final activityContent = snapshot.data?.docs
.map((data) => ActivityModel.fromFirestore(data))
.toList();
return Scrollbar(
controller: widget.scrollController,
child: ListView.builder(
shrinkWrap: true,
controller: widget.scrollController,
itemCount: activityContent!.length,
itemBuilder: (context, i) {
return FutureBuilder(
future: FirebaseFirestore.instance
.collection('users')
.where('uid', whereIn: activityContent[i].players)
.get(),
builder: (BuildContext context,
AsyncSnapshot<QuerySnapshot> snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return const SizedBox(
height: 65.0,
);
}
final users = snapshot.data!.docs.map((e) {
return UserModel.fromFirestore(e);
}).toList();
return MyWidget(
users: users,
);
},
);
},
),
);
}
},
);

Handling multiple futures in Build

This is my build:
#override
Widget build(BuildContext context) {
return FutureBuilder<Position>(
future: _init,
builder: (context, snapshot) {
...
final Position position = snapshot.data!;
return FlutterMap(
...
layers: [
...
MarkerLayerOptions(
markers: markers, //this is the future list
),
],
);
});
}
Now, markers is a Future and I build it with this methods:
late Future<List<Marker>> markers;
#override
void initState() {
...
markers = getMarkers();
}
Future<List<Marker>> getMarkers() async {
List<Marker> markerTemp = [];
for (var friend in friendsList) {
DocumentSnapshot document = await locationRef.doc(friend).get();
if (document.exists)
markerTemp.add(Marker(...));
}
return markerTemp;
}
So when I run my application I get an error saying that markers is not initialized. How can I have my list ready when called in the build method?
I tried things like nested FutureBuilder or using Future.wait([item1,item2]) but since I'm newbie to this language I'm having troubles implementing it the right way probably
Try using FutureBuilder in some way similar to this:
return FutureBuilder<List<Marker>>(
future: markers,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting)
return Center(child: CircularProgressIndicator());
if (snapshot.hasError)
return Text("${snapshot.error}");
// You can access the list here, use this newly created list
List<Markers> markerList = snapshot.data as List<Marker>;
return FutureBuilder<Position>(...)
});
I believe FutureBuilder will solve your problem. try this:
#override
Widget build(BuildContext context) {
return FutureBuilder(
initialData: null,
future: Future.wait(getMarkers()),
builder: (context, snapshot) {
...
},
);
}
it's that simple. if something goes wrong just make sure your getMarkers() actually return a list of Futures and you'll be alright

The body might complete normally, causing 'null' to be returned, but the return type is a potentially non-nullable type. / Flutter

I just started writing with Flutter.I am constantly getting this error. What should i do? Here is my code:
#override
Widget build(BuildContext context) {
return Scaffold(
body: FutureBuilder<WeatherInfo>(
future: futureWeather,
builder: (context, snapshot) {
if (snapshot.hasData) {
} else if (snapshot.hasError) {
return Center(
child: Text("${snapshot.error}"),
);
#override
Widget build(BuildContext context) {
return Scaffold(
body: FutureBuilder<WeatherInfo>(
future: futureWeather,
builder: (context, snapshot) {
if (snapshot.hasData) {
} else if (snapshot.hasError) {
return Center(
child: Text("${snapshot.error}"),
);
}
return const Center(child: CircularProgressIndicator());
}));
}
Inside the FutureBuilder you covered the case in which you have the data or you have an error, but not when you are expecting for the future to complete(And don't have neither the data or an error).
I just added a Circular progress indicator to be shown while no data or no error are returned from the Future, that should prevent the FutureBuilder from returning null. And when the snapshot state changes the data or error would be shown.
I think you should try and wrap the future builder in a container

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
}