Flutter StreamBuilder vs FutureBuilder - flutter

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.

Related

How to update the initial state on Flutter

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

Update list item periodically using stream with Flutter

I'm writing an app that communicates with a server. The app will have a listview with items inside that need to be updated periodically (every x seconds) and I'm trying to figure out the best way to accomplish this.
Let's say I have a Stream that sends a request to a server every 5 seconds. I yield the result, but how can I receive this data inside of a view and update it?
for example:
Stream:
Stream<double> progress(int id) async* {
while (true) {
await Future.delayed(const Duration(seconds: 5));
double progress = await api.getProgressFor(id: id);
yield progress;
}
}
How could I create a widget, say a LinearProgressIndicator that will listen for yields from this stream and update when they are sent.
The best way is to use a StreamBuilder. Here is a sample showing where you call your stream and where you display your ListView or similar.
#override Widget build(BuildContext context) {
return StreamBuilder <int>(
stream: callProgressStream ,
builder: (context, snapshot) {
if (!snapshot.hasData) return CircularProgressIndicator();
else {
// Your code here
return ListView();}
});
}
Let me know if this does not help.

How to persist value from a Future when switching between pages in Flutter?

I am trying to add a widget to the screen when the future has data in Screen 1.
class UserChoiceBooks extends StatelessWidget {
final String title;
UserChoiceBooks({Key key, this.title}) : super(key: key);
#override
Widget build(BuildContext context) {
return FutureBuilder(
future: Provider.of<Books>(context, listen: false)
.getRecommendedBooks("test"),
builder: (ctx, snapshot) {
// Checking if future is resolved
if (snapshot.connectionState == ConnectionState.done) {
// If we got an error
if (snapshot.hasError) {
return Center(
child: Text(
'${snapshot.error} occured',
style: TextStyle(fontSize: 18),
),
);
// if we got our data
} else if (snapshot.hasData) {
// Extracting data from snapshot object
final List<Book> recommendedBooksML = snapshot.data;
return BookList(title, recommendedBooksML);
} else {
return SizedBox.shrink();
}
} else {
return Container();
}
}
// ... some code here
);
}
}
I navigate to a different screen Screen 2.
And then back to Screen 1. I loose the future data and it starts Building again.
PS:I found a temporary workaround by storing the Future data in a Global Variable
List<Book> placeholder=[];
And then setting the value from future...placeholder=snapshot.data;
When Switching between Screen 1 and 2. I am checking
placeholder==[]?UserChoiceBooks (title):BookList(title,placeholder)
Is there a better way to keep data from snapshot so that switching between screen future value is not Lost?
You are making a call during your build:
Provider.of<Books>(context, listen: false).getRecommendedBooks("test")
Thus when the screen is rebuilt, it is invoked again. You are only too lucky that no other rebuilds are happening between those two navigations, because in theory they are possible.
A recommended mindset is that a rebuild may happen at each frame, and non-rebuilding frames should be treated as a framework optimization.
So you should make a call one time and save it. There are a few options:
Create Books somewhere up the tree and pass it to the widget. Then make your widget stateful and call widget.books.getRecommendedBooks in its initState().
If Books is a singleton, you may use a service locator like https://pub.dev/packages/get_it to instantiate it once and then use GetIt.instance.get to fetch this instance anywhere in your widget. A service locator pattern has some advantages over the provider pattern.
A nasty way is to call the future in a stateful widget's state and call the method on first build if _future == null.
For stateful widgets see this tutorial: https://flutter.dev/docs/development/ui/interactive#stateful-and-stateless-widgets
Also note that you should use a key for such a widget that would be different for different titles that you pass to the constructor. Otherwise, when you replace your widget with one with another title, the framework would not know that all that follows is for another book and will not create a new state, thus initState() will not be called.

BlocBuilder not updating after cubit emit

UPDATE After finding the onChange override method it appears that the updated state is not being emitted #confused
UPDATE 2 Further debugging revealed that the StreamController appears to be closed when the updated state attempts to emit.
For some reason 1 of my BlocBuilders in my app refuses to redraw after a Cubit emit and for the life of me I cannot figure out why, there are no errors when running or debugging, the state data is being updated and passed into the emit.
Currently, it is a hook widget, but I have tried making it Stateless, Stateful, calling the cubit method in an effect, on context.bloc.
I have tried making it a consumer, nothing makes it into the listen, even tried messing with listenWhen and buildWhen and nothing is giving any indication as to why this is not building.
It renders the loading state and that is where it ends.
Widget:
class LandingView extends HookWidget {
#override
Widget build(BuildContext context) {
final hasError = useState<bool>(false);
return Scaffold(
key: const Key(LANDING_VIEW_KEY),
body: BlocProvider<CoreCubit>(
create: (_) => sl<CoreCubit>()..fetchDomainOptions(),
child: BlocBuilder<CoreCubit, CoreState>(
builder: (context, state) {
switch (state.status) {
case CoreStatus.loaded:
return LandingViewLoaded();
case CoreStatus.error:
return LandingViewError();
case CoreStatus.loading:
default:
return AppIcon();
}
},
),
),
);
}
}
Cubit method:
Future<void> fetchDomainOptions() async {
final inputEither = await getDomainOptions();
return inputEither.fold(
_handleFailure,
(options) {
emit(state.copyWith(
domainOptions: options,
status: CoreStatus.loaded,
));
},
);
}
I have a few other widgets that work off freezed data classes and work of the same status key logic without any issues, I even went as far as trying to add a lastUpdated timestamp key onto it to make even more data change, but in the initial state domainOptions is null and status is CoreStatus.loading, which should already be enough to trigger a UI update.
TIA
I haven't figured out yet why exactly this happens but I believe we're dealing with some kind of race condition here.
Also, I don't know the proper solution to work around that but I have found that delaying calling emit() for a short amount of time prevents this issue from occurring.
void load() async {
try {
emit(LoadingState());
final vats = await provider.all();
await Future<void>.delayed(const Duration(milliseconds: 50));
emit(LoadedState(vats));
} catch (e) {
print(e.toString());
emit(ErrorState());
}
}
Add await Future<void>.delayed(const Duration(milliseconds: [whatever is needed])); before emitting your new state.
I came to this conclusion after reading about this issue on cubit's GitHub: Emitted state not received by CubitBuilder
i resolved my problem, i was using getIt.
i aded the parameter bloc.
my problem was not recibing the events (emit) of the cubit (bloc)
child: BlocBuilder<CoreCubit, CoreState>(
bloc: getIt<CoreCubit>(), // <- this
builder: (context, state) {
},
),

Flutter StreamBuilder Called Twice When Initialized

Is StreamBuilder always called twice? Once for initial data and then once for the input stream?
Initializing the following StreamBuilder shows that the build method is called twice. The second call is 0.4 seconds after the first one.
Stream: Build 1566239814897
Stream: Build 1566239815284
import 'dart:async';
import 'dart:ui';
import 'package:flutter/material.dart';
import 'package:nocd/utils/bloc_provider.dart';
void main() =>
runApp(BlocProvider<MyAppBloc>(bloc: MyAppBloc(), child: MyApp()));
class MyAppBloc extends BlocBase {
String _page = window.defaultRouteName ?? "";
/// Stream for [getPage].
StreamController<String> pageController = StreamController<String>();
/// Observable navigation route value.
Stream get getPage => pageController.stream;
MyAppBloc() {}
#override
void dispose() {
pageController.close();
}
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
final MyAppBloc myAppBloc = BlocProvider.of<MyAppBloc>(context);
return StreamBuilder(
stream: myAppBloc.getPage,
initialData: "Build",
builder: (context, snapshot) {
print("Stream: " +
snapshot.data +
DateTime.now().millisecondsSinceEpoch.toString());
return Container();
},
);
}
}
Why is the StreamBuilder called twice?
Streambuilder will be called 2 times, first for Initial and second for the stream. And data is only changed when state is ConnectionState.active.
kinldy see the official doc example.
StreamBuilder<int>(
//stream:fire, // a Stream<int> or null
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
},
);
StreamBuilder documentation
The initial snapshot data can be controlled by specifying initialData. This should be used to ensure that the first frame has the expected value, as the builder will always be called before the stream listener has a chance to be processed.
initialData
Providing this value (presumably obtained synchronously somehow when the Stream was created) ensures that the first frame will show useful data. Otherwise, the first frame will be built with the value null, regardless of whether a value is available on the stream: since streams are asynchronous, no events from the stream can be obtained before the initial build.
StreamBuilder makes two build calls when initialized, once for the initial data and a second time for the stream data.
Streams do not guarantee that they will send data right away so an initial data value is required. Passing null to initialData throws an InvalidArgument exception.
StreamBuilders will always build twice even when the stream passed is null.
Update:
A detailed technical explanation of why StreamBuilders build multiple times even when an initalData is provided can be found in this Flutter issue thread: https://github.com/flutter/flutter/issues/16465
It's not possible for a broadcast stream to have an initial state. Either you were subscribed when the data was added or you missed it. In an async single-subscription stream, any listen calls added won't be invoked until either the next microtask or next event loop (can't remember, may depend), but at any rate there is no way to get the data out the stream on the current frame. - jonahwilliams
As was said above, you just need to place your code inside Connection.Active state. See below:
StreamBuilder<QuerySnapshot>(
stream: historicModel.query.snapshots(),
builder: (context, stream){
if (stream.connectionState == ConnectionState.waiting) {
return Center(child: CircularProgressIndicator());
} else if (stream.hasError) {
return Center(child: Text(stream.error.toString()));
} else if(stream.connectionState == ConnectionState.active){
//place your code here. It will prevent double data call.
}