Flutter StreamBuilder Called Twice When Initialized - flutter

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.
}

Related

How can i listen to a stream and then pass it to Streambuilder?

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

How to implement FutureBuilder with already obtained future

I'm a little confused about how to implement Flutter's FutureBuilder according to the docs. I have a Future Builder here which work, with the only problem being that it every time the build method is run so to is the future being used:
FutureBuilder(
future: DBProvider.db.getUser(),
builder: (_, userData) {
switch (userData.connectionState) {
case ConnectionState.none:
return Container();
case ConnectionState.waiting:
return Container();
case ConnectionState.active:
case ConnectionState.done:
newUser = userData.data;
return ListView(
shrinkWrap: true,
children: <Widget>[
... // Lot's of things
],
);
}
return null;
},
),
I then realize the importance of this part of the docs where it says the future must be "obtained earlier" so this problem doesn't happen:
The future must have been obtained earlier, e.g. during
State.initState, State.didUpdateConfig, or
State.didChangeDependencies. It must not be created during the
State.build or StatelessWidget.build method call when constructing the
FutureBuilder. If the future is created at the same time as the
FutureBuilder, then every time the FutureBuilder's parent is rebuilt,
the asynchronous task will be restarted.
Here's where my question comes from, I understand what those words are saying, but in terms of writing the code according to that what exactly does mean / look like?
Do I simply make a function and call it in initState like this:
#override
void initState() {
super.initState();
_getUser();
}
_getUser() async {
newUser = await DBProvider.db.getUser();
}
In this case what goes in the future argument of the FutureBuilder? Or this completely not right?
Yes, you are on the right track. The easiest way to deal with this is to store a Future variable in your state. In initState make the API call and store the Future. You can then pass this Future to FutureBuilder.
Something like this:
class MyWidget extends StatefulWidget {
State createState() {
return MyState();
}
}
class MyState extends State<MyWidget> {
Future userFuture;
void initState() {
super.initState();
// Note here we are not awaiting the user, but rather storing
// the future in the variable
userFuture = DBProvider.db.getUser();
}
Widget build(BuildContext context) {
return FutureBuilder(
future: userFuture,
builder: (BuildContext, snapshot) {
...
}
);
}
}

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

How to reference Firestore documentID in a Flutter FutureBuilder

I have working code that gets a collection ('songs') from Firestore using a Future and a QuerySnapshot. I have that in a small function getSongs(). While I'm inside that function I have access to the documents' IDs ... so if I call say:
print(songsQuery.documents[1].documentID);
I get -LSvpZxM2pUIYjjp0qby
But later in my code I use a FutureBuilder where I call getSongs() for the future: and then build out a ListView with tiles of song info (Artist, Title, etc) from the snapshot in the builder:.
While I'm now in this widget I can't seem to figure out how to reference my .documentID anymore. I can get to all the .data elements for each document...but not the actual documentID.
Is there something very obvious that I'm missing?
Thanks for any help.
ER
I have scoured the internet trying to resolve with no luck. It seems like many people take the list of documents, load them into an array, add the doc.id, push it all into an array of items. Then use items. I would like to just use the snapshots as rendered back from Firestore and reference the doc.id directly if possible.
import 'package:flutter/material.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'dart:async';
class AllSongs extends StatelessWidget {
Future getSongs() async {
var firestore = Firestore.instance;
QuerySnapshot songsQuery = await firestore.collection('songs').getDocuments();
print(songsQuery.documents[1].documentID);
//Here I can get to documentID...
return songsQuery.documents;
}
#override
Widget build(BuildContext context) {
return Center(
child: FutureBuilder(
future: getSongs(),
builder: (_, songSnapshots){
print('How do I reference the DocumentID in here?');
print(songSnapshots.data.length);
print(songSnapshots.data[0].data['title']);
//print(songSnapshots.data[0].documentID);
//print(songSnapshots.data[0].ref);
//print(songSnapshots.data[0].data[ DOCUMENTID?? ]);
if(songSnapshots.connectionState == ConnectionState.waiting){
return Center(
child: Text('Loading...'),
);
} else {
return ListView.builder(
itemCount: songSnapshots.data.length ,
itemBuilder: (_, index){
return ListTile(
title: Text(songSnapshots.data[index].data['title']),
subtitle: Text(songSnapshots.data[index].data['artist']),
);
});
}
},
)
);
}
}
You're trying to access the value the value of the future before it resolves.
Try adding this line:
if (!songSnapshots.hasData) {
// Future hasn't resolved
return something;
}
// Future has resolved, you can access your data (including documentId)
Your future resolves to a list of DocumentSnapshot so just wait for the future to resolve and you should have access to all your data. Alternatively, you can try to access this inside your else statement where the state of the connection is not waiting, but in this case you are considering any non-waiting states as successful so I'd recommend using the hasData property of the AsyncSnapshot class instead.

Flutter StreamBuilder vs FutureBuilder

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.