Reputation: 2343
StreamBuilder
is rebuild whenever it get new event. This cause problem with for example navigation (Navigator.push
) because if new event is receive while navigate then this trigger rebuild. Because try to navigate while widget tree still being built, this will throw error.
It is not possible to prevent rebuild to avoid this issue as required.
Suggested workaround is basically take stream from cache. Also: here and here
But this mean cannot have StreamBuilder build list which constantly update if also want to provide navigation from cards on list. For example in card onPressed()
. See here.
So to refresh data must use pull to refresh…
Anyone have better solution? Or is Flutter team work on solve this limitation for example by allow prevent rebuild if card is tap by user?
UPDATE:
TL;DR Is pull to refresh only way to update data since stream in StreamBuilder must be cached to prevent it rebuilding every time new event is received?
UPDATE 2:
I have try implement cache data but my code not work:
Stream<QuerySnapshot> infoSnapshot;
fetchSnapshot() {
Stream<QuerySnapshot> infoSnapshot = Firestore.instance.collection(‘info’).where(‘available’, isEqualTo: true).snapshots();
return infoSnapshot;
}
@override
void initState() {
super.initState();
fetchSnapshot();
}
...
child: StreamBuilder(
stream: infoSnapshot,
builder: (context, snapshot) {
if(snapshot.hasData) {
return ListView.builder(
itemBuilder: (context, index) =>
build(context, snapshot.data.documents[index]),
itemCount: snapshot.data.documents.length,
);
} else {
return _emptyStateWidget();
}
UPDATE 3:
I have try use StreamController
but cannot implement correct:
Stream<QuerySnapshot> infoStream;
StreamController<QuerySnapshot> infoStreamController = StreamController<QuerySnapshot>();
@override
void initState() {
super.initState();
infoStream = Firestore.instance.collection(‘info’).where(‘available’, isEqualTo: true).snapshots();
infoStreamController.addStream(infoStream);
}
…
child: StreamBuilder(
stream: infoStreamController.stream,
builder: (context, snapshot) {
UPDATE 4:
Suggestion to use _localStreamController
give error:
StreamController<QuerySnapshot> _localStreamController = StreamController<QuerySnapshot>();
@override
void initState() {
super.initState();
Firestore.instance.collection(‘info’).snapshots().listen((QuerySnapshot querySnapshot) {
// if(userAdded == null) {
_localStreamController.add(querySnapshot);
// }
});
...
child: StreamBuilder(
stream: _localStreamController.stream,
builder: (context, snapshot) {
The getter 'stream' was called on null.
The method 'add' was called on null.
Upvotes: 4
Views: 4301
Reputation: 31
Try breaking everything into widgets
Running the query should cache it even if you fully close your app(I believe only cache it on fully closed for up to 30 minutes but if you remain without internet connection, you still have access to past previous cached queries from Firestore)
Try something like this:
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Please work')),
body: _buildStream(context),
);
}
Widget _buildStream(BuildContext context) {
return StreamBuilder(
stream: yourFireStoreStream,
builder: (context, snapshot) {
if (!snapshot.hasData) return LinearProgressIndicator();
return _buildAnotherwidget(context, snapshot.data.documents);
},
);
}
Widget _buildAnotherwidget(Buildcontext context, List<DocumentSnapshot> snaps){
return ListView.Builder(
itemCount: snaps.length,
itemBuilder:(context, index) {
..dostuff here...display your cards etc..or build another widget to display cards
}
);
}
focus on the breaking into more widgets. The highest part should have the streambuilder along with the stream. then go deep down into more widgets. The streambuilder automatically will listen and subscribe to the given stream.
When streambuilder updates, it will update the lower widgets.
Now this way, when you tap on a card in a lower widget to navigate, it should not affect the highest widget because it will effect only the UI.
we placed the streambuilder in its own top level widget...
I hope i made some sense :(
I wrote the code out without testing but im sure you can get it to work
Upvotes: 0
Reputation: 4346
It seems like the actual problem based on your comments above is that it crashes after you navigate away from the view using the stream. You have to either:
Update: Adding code with pseudo example
class Widget {
// Your local stream
Stream<String> _localStream;
// Value to indicate if you have navigated away
bool hasNavigated = false;
...
void init() {
// subscribe to the firebase stream
firebaseStream...listen((value){
// If this value is still false then emit the same value to the localStream
if(!hasNavigated) {
_localStream.add(value);
}
});
}
Widget build() {
return StreamBuilder(
// subscribe to the local stream NOT the firebase stream
stream: _localStream,
// handle the same way as you were before
builder: (context, snapshot) {
return YourWidgets();
}
);
}
}
Upvotes: 2