I am trying to figure out the best way to update a ListView.builder() while I'm fetching a list of data. Essentially, I am downloading data in batches -- let's say a group of 10 images at a time -- and displaying them in a ListView.builder after the future completes, with an indicator below it to signify that we're still fetching data. And do this until everything is fetched.
What's the best way of going about this?
Example code of what I have:
void _fetchImages() async {
// Fetch images
for (...) {
final results = await Future.wait[imageFutures];
// update list here
imageList.addAll(results); // let's say data comes back in correct format
setState((){});
}
}
#override
void initState() {
super.initState();
_fetchImages();
}
#override
Widget build(BuildContext context) {
return ListView.builder(...);
}
Return List from Stream then you can use StreamBuilder.
return StreamBuilder<List<MyImage>>(
stream: dataStream,
builder: (BuildContext context, AsyncSnapshot<List<MyImage>> snapshot) {
if (snapshot.hasData) {
return Center(
child: ListView.builder(
...
);
}
return SomeWidget(
...
);
},
);
I'm building a flutter page that shows to the users a list of the credit cards that are stored on the back-end and lets the user delete already existing cards.
To fetch the cards from the back-end I'm using initState(). Note that controller.getCreditCards() returns a Future<List<CreditCardSummary>>:
#override
void initState() {
super.initState();
_futureCreditCards = controller.getCreditCards();
}
This List is then rendered using a FutureBuilder, just like the documentation recommends:
Widget build(BuildContext context) {
return FutureBuilder<List<CreditCardSummary>>(
future: _futureCreditCards,
builder: (context, snapshot) {
if (snapshot.hasData) {
return Scaffold(
// My page that renders all the cards goes here
// Inside this future builder I can access the cards list with snapshot.data
);
} else if (snapshot.hasError) {
return Text('ERROR');
}
// Show a loading screen while the data is beeing fetched:
return const CircularProgressIndicator();
});
}
This is all working fine, the problem only begins when I need to update this data. For example, when the user deletes a creditCard, I want to delete the card from the List and to re-render the page with the new version of the List, but I don't know a good way of doing that.
In order to get updated data/ refresh the FutureBuilder, you need to reassign the future variable.
For your case, when ever you like to update,
_futureCreditCards = controller.getCreditCards();
setState((){});
Currently I'm listening to a websocket stream in a Streambuilder. When i receive data, I modify the data in the streambuilder itself and pass it to the widget. But since build is called multiple times, the same received data is processed multiple times. So I want to perform processing of data(done in receivedMessage(data)) outside of Streambuilder.So to avoid firing of Streambuilder multiple times, i'm trying to take data processing away from the widget. Is this the right approach, how should i do it?
class _DrawingPageState extends State<DrawingPage> {
void initState() {
super.initState();
channel = IOWebSocketChannel.connect(ipVal);
_stream = channel.stream;
}
#override
Widget build(BuildContext context) {
child: StreamBuilder(
stream: _stream,
builder: (context, snapshot) {
if(snapshot.hasData){
data = receivedMessage(data);
print("Received Message");
return Text(data);
}
else if(snapshot.hasError){
print(snapshot.error);
}
return Text(data);
}
),
}
}
I have a group profile page, where a user can change the description of a group. He clicks on the description, gets on a new screen and saves it to Firestore. He then get's back via Navigator.pop(context) to the group profile page which lists all elements via FutureBuilder.
First, I had the database request for my FutureBuilder inside the main build method (directly inside future builder 'future: request') which was working but I learnt it is wrong. But now I have to wait for a rebuild to see changes. How do I tell FutureBuilder that there is a data update?
I am loading Firestore data as follows within the group profile page:
Future<DocumentSnapshot> _future;
#override
void initState() {
super.initState();
_getFiretoreData();
}
Future<void> _getFiretoreData() async{
setState(() {
this._future = Firestore.instance
.collection('users')
.document(globals.userId.toString())
.get();});
}
The FutureBuilder is inside the main build method and gets the 'already loaded' future like this:
FutureBuilder(future: _future, ...)
Now I would like to tell him: a change happened to _future, please rebuild ;-).
Ok, I managed it like this (which took me only a few lines of code). Leave the code as it is and get a true callback from the navigator to know that there was a change on the second page:
// check if second page callback is true
bool _changed = await Navigator.push(
context,
MaterialPageRoute(
builder: (context) =>
ProfileUpdate(userId: globals.userId.toString())),
);
// if it's true, reload future data
_changed ? _getFiretoreData() : Container();
On the second page give the save button a Navigator.pop(context, true).
i would advice you not to use future builder in this situation and use future.then() in an async function and after you get your data update the build without using future builder..!
Future getData() async {
//here you can call the function and handle the output(return value) as result
getFiretoreData().then((result) {
// print(result);
setState(() {
//handle your result here.
//update build here.
});
});
}
How about this?
#override
Widget build(BuildContext context) {
if (_future == null) {
// show loading indicator while waiting for data
return Center(child: CircularProgressIndicator());
} else {
return YourWidget();
}
}
You do not need to set any state. You just need to return your collection of users in your GetFirestoreData method.
Future<TypeYouReturning> _getFirestoreData() async{
return Firestore.instance
.collection('users')
.document(globals.userId.toString())
.get();
}
Inside your FutureBuilder widget you can set it up something like Theo recommended, I would do something like this
return FutureBuilder(
future: _getFirestoreData(),
builder: (context, AsyncSnapshot<TypeYouReturning> snapshot) {
if (!snapshot.hasData) {
return Center(
child: CircularProgressIndicator(),
);
} else {
if (snapshot.data.length == 0)
return Text("No available data just yet");
return Container();//This should be the desire widget you want the user to see
}
},
);
Why don't you use Stream builder instead of Future builder?
StreamBuilder(stream: _future, ...)
You can change the variable name to _stream for clarity.
What is the main difference between StreamBuilder and FutureBuilder.
What to use and when to use?
What are the tasks they are intended to perform?
How each of them listens to changes in a dynamic list?
Both StreamBuilder and FutureBuilder have the same behavior: They listen to changes on their respective object. And trigger a new build when they are notified
of a new value.
So in the end, their differences are how the object they listen to works.
Future is like Promise in JS or Task in c#. They are the representation of an asynchronous request. Futures have one and only one response. A common usage of Future is to handle HTTP calls. What you can listen to on a Future is its state. Whether it's done, finished with success, or had an error. But that's it.
Stream on the other hand is like async Iterator in JS. This can be assimilated to a value that can change over time. It usually is the representation of web-sockets or events (such as clicks). By listening to a Stream you'll get each new value and also if the Stream had an error or completed.
How each of them listens to changes in a dynamic list?
A Future can't listen to a variable change. It's a one-time response. Instead, you'll need to use a Stream.
FutureBuilder is used for one time response, like taking an image from Camera, getting data once from native platform (like fetching device battery), getting file reference, making an http request etc.
On the other hand, StreamBuilder is used for fetching some data more than once, like listening for location update, playing a music, stopwatch, etc.
Here is full example mentioning both cases.
FutureBuilder solves a square value and returns the result after 5 seconds, till then we show progress indicator to the user.
StreamBuilder shows a stopwatch, incrementing _count value by 1 every second.
void main() => runApp(MaterialApp(home: HomePage()));
class HomePage extends StatefulWidget {
#override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
int _count = 0; // used by StreamBuilder
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
_buildFutureBuilder(),
SizedBox(height: 24),
_buildStreamBuilder(),
],
),
);
}
// constructing FutureBuilder
Widget _buildFutureBuilder() {
return Center(
child: FutureBuilder<int>(
future: _calculateSquare(10),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done)
return Text("Square = ${snapshot.data}");
return CircularProgressIndicator();
},
),
);
}
// used by FutureBuilder
Future<int> _calculateSquare(int num) async {
await Future.delayed(Duration(seconds: 5));
return num * num;
}
// constructing StreamBuilder
Widget _buildStreamBuilder() {
return Center(
child: StreamBuilder<int>(
stream: _stopwatch(),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.active)
return Text("Stopwatch = ${snapshot.data}");
return CircularProgressIndicator();
},
),
);
}
// used by StreamBuilder
Stream<int> _stopwatch() async* {
while (true) {
await Future.delayed(Duration(seconds: 1));
yield _count++;
}
}
}
I find that sometimes real-world analogies work well for explaining / remembering concepts. Here's one - it's not perfect but it helps me.
Think that you are at one of those modern sushi restaurants where you have a belt going around the room with sushi boats on it. You just sit down and wait till one goes by, grab it and eat. But they also allow you to order carry out.
A Future is like the token with a number on it that they give you when you order takeout; you made the request, but the result is not yet ready but you have a placeholder. And when the result is ready, you get a callback (the digital board above the takeout counter shows your number or they shout it out) - you can now go in and grab your food (the result) to take out.
A Stream is like that belt carrying little sushi bowls. By sitting down at that table, you've "subscribed" to the stream. You don't know when the next sushi boat will arrive - but when the chef (message source) places it in the stream (belt), then the subscribers will receive it. The important thing to note is that they arrive asynchronously (you have no idea when the next boat/message will come) but they will arrive in sequence (i.e., if the chef puts three types of sushi on the belt, in some order -- you will see them come by you in that same order)
From a coding perspective -- both Futures and Streams help you deal with asynchrony (where things don't happen instantly, and you don't know when you will get a result after you make a request).
The difference is that Futures are about one-shot request/response (I ask, there is a delay, I get a notification that my Future is ready to collect, and I'm done!) whereas Streams are a continuous series of responses to a single request (I ask, there is a delay, then I keep getting responses until the stream dries up or I decide to close it and walk away).
Hope that helps.
FutureBuilder and StreamBuilder behave similarly: they listen for changes in their respective objects. In response to changing value notifications, a new build is triggered.
Ultimately, the difference lies in how they listen to async calls.
FutureBuilder
There is only one response to it. Futures are commonly used in http calls. The Future can be used to listen to the state, e.g., when it has completed fetching the data or had an error.
like as example link here.
StreamBuilder
As opposed to streams, which are iterators that can assimilate different values, which will change over time. Each new value is returned by Stream along with an error message or success message if it has any.
like as example link here.
Conclusion
The following data might help you understand the above better:
If your use case is to just get the data, and display it, like Total number of courses from a class from API. Then you can use FutureBuilder.
What if, the data updates every second or minute, while you use the app, like upcoming posts in a blog or increase comments on the blog or increase in likes on the blog. It updates asynchronously at certain interval, in that case StreamBuilder is the best option.
Bases upon the use case, you decide which one to use. Both of them are good in their own way.
Here is a full example mentioning both cases.
FutureBuilder solves a square value and returns the result after 5 seconds, till then we show a progress indicator to the user.
StreamBuilder shows a stopwatch, incrementing _count value by 1 every second.
void main() => runApp(MaterialApp(home: HomePage()));
class HomePage extends StatefulWidget {
#override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
int _count = 0; // used by StreamBuilder
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
_buildFutureBuilder(),
SizedBox(height: 24),
_buildStreamBuilder(),
],
),
);
}
// constructing FutureBuilder
Widget _buildFutureBuilder() {
return Center(
child: FutureBuilder<int>(
future: _calculateSquare(10),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done)
return Text("Square = ${snapshot.data}");
return CircularProgressIndicator();
},
),
);
}
// used by FutureBuilder
Future<int> _calculateSquare(int num) async {
await Future.delayed(Duration(seconds: 5));
return num * num;
}
// constructing StreamBuilder
Widget _buildStreamBuilder() {
return Center(
child: StreamBuilder<int>(
stream: _stopwatch(),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.active)
return Text("Stopwatch = ${snapshot.data}");
return CircularProgressIndicator();
},
),
);
}
// used by StreamBuilder
Stream<int> _stopwatch() async* {
while (true) {
await Future.delayed(Duration(seconds: 1));
yield _count++;
}
}
}
Both StreamBuilder and FutureBuilder widgets in Flutter allow you to build reactive UIs that respond to asynchronous data changes. However, they have some differences in terms of their usage and the type of data they work with.
FutureBuilder widget is used when you want to asynchronously retrieve a single piece of data that will not change over time, such as a network request for user information. It expects a Future as its data source, and when the Future completes, it rebuilds the widget tree with the resulting data.
StreamBuilder widget, on the other hand, is used when you want to display data that can change over time, such as a real-time chat application. It expects a Stream as its data source, and whenever new data is available, it rebuilds the widget tree with the updated data.
Here are some other differences:
FutureBuilder has a single AsyncSnapshot that represents the current state of the Future, while StreamBuilder has multiple AsyncSnapshots, each representing a new piece of data emitted by the Stream.
FutureBuilder will execute the Future every time the widget is rebuilt, while StreamBuilder will only subscribe to the Stream once when the widget is mounted, and unsubscribe when the widget is disposed.
Here's an example of using FutureBuilder:
FutureBuilder<String>(
future: fetchData(),
builder: (BuildContext context, AsyncSnapshot<String> snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
return Text(snapshot.data);
} else {
return CircularProgressIndicator();
}
},
);
And here's an example of using StreamBuilder:
StreamBuilder<int>(
stream: countStream(),
builder: (BuildContext context, AsyncSnapshot<int> snapshot) {
if (snapshot.hasData) {
return Text('Count: ${snapshot.data}');
} else {
return CircularProgressIndicator();
}
},
);
In summary, FutureBuilder is used for one-time asynchronous data retrieval, while StreamBuilder is used for displaying continuously updating data.