I have been trying to listen to more than one collection from Firestone using a StreamBuilder
or something similar.
My original code when I was working with only one Stream was:
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
class List extends StatefulWidget{
///The reference to the collection is like
///Firestore.instance.collection("users").document(firebaseUser.uid).collection("list1").reference()
final CollectionReference listReference;
List(this.listReference);
@override
State createState() => new ListState();
}
class ListState extends State<List> {
@override
Widget build(BuildContext context){
return new StreamBuilder(
stream: widget.listReference.snapshots(),
builder: (context, snapshot) {
return new ListView.builder(
itemCount: snapshot.data.documents.length,
padding: const EdgeInsets.only(top: 2.0),
itemExtent: 130.0,
itemBuilder: (context, index) {
DocumentSnapshot ds = snapshot.data.documents[index];
return new Data(ds);
}
);
});
}
}
This code works fine, but now I want to listen to more than one collection. I have come across a solution that doesn't involve a StreamBuilder and works with a dynamic list. My code now looks like this:
import 'dart:async';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'main.dart';
import 'package:async/async.dart';
class ListHandler extends StatefulWidget{
final CollectionReference listReference;
ListHandler(this.listReference);
@override
State createState() => new ListHandlerState();
}
class ListHandlerState extends State<ListHandler> {
StreamController streamController;
List<dynamic> dataList = [];
@override
void initState() {
streamController = StreamController.broadcast();
setupData();
super.initState();
}
@override
void dispose() {
super.dispose();
streamController?.close();
streamController = null;
}
Future<Stream> getData() async{
Stream stream1 = Firestore.instance.collection("users").document(firebaseUser.uid).collection("list1").snapshots();
Stream stream2 = Firestore.instance.collection("users").document(firebaseUser.uid).collection("list2").snapshots();
return StreamZip(([stream1, stream2])).asBroadcastStream();
}
setupData() async {
Stream stream = await getData()..asBroadcastStream();
stream.listen((snapshot) {
setState(() {
//Empty the list to avoid repetitions when the users updates the
//data in the snapshot
dataList =[];
List<DocumentSnapshot> list;
for(int i=0; i < snapshot.length; i++){
list = snapshot[i].documents;
for (var item in list){
dataList.add(item);
}
}
});
});
}
@override
Widget build(BuildContext context){
if(dataList.length == 0){
return new Text("No data found");
}
return new ListView.builder(
itemCount: dataList.length,
padding: const EdgeInsets.only(top: 2.0),
itemBuilder: (context, index) {
DocumentSnapshot ds = dataList[index];
return new Data(ds['title']);
}
);
}
}
The thing is that the ListView returns Data
that is a StatefulWidget
and the user can interact with it making the data change in Firestore making the next error appear:
[VERBOSE-2:dart_error.cc(16)] Unhandled exception:
setState() called after dispose(): ListHandlerState#81967(lifecycle state: defunct, not mounted)
This error happens if you call setState() on a State object for a widget that no longer appears in the widget tree (e.g., whose parent widget no longer includes the widget in its build). This error can occur when code calls setState() from a timer or an animation callback. The preferred solution is to cancel the timer or stop listening to the animation in the dispose() callback. Another solution is to check the "mounted" property of this object before calling setState() to ensure the object is still in the tree.
This error might indicate a memory leak if setState() is being called because another object is retaining a reference to this State object after it has been removed from the tree. To avoid memory leaks, consider breaking the reference to this object during dispose().
The app does not crash, and it does what is expected but it always shows this error.
Some people use the library rxdart
to work with streams and I have tried doing something like the code below but when I put it in the StreamBuilder only elements from on of the :
import 'dart:async';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'main.dart';
import 'showInfo.dart';
import 'package:rxdart/rxdart.dart';
class ListHandler extends StatefulWidget{
@override
State createState() => new ListHandlerState();
}
class ListHandlerState extends State<ListHandler> {
Stream getData() {
Stream stream1 = Firestore.instance.collection("users").document(firebaseUser.uid).collection("list1").snapshots();
Stream stream2 = Firestore.instance.collection("users").document(firebaseUser.uid).collection("list2").snapshots();
return Observable.merge(([stream2, stream1]));
}
@override
Widget build(BuildContext context){
return new StreamBuilder(
stream: getData(),
builder: (context, snapshot) {
if(!snapshot.hasData){
print(snapshot);
return new Text("loading");
}
return new ListView.builder(
itemCount: snapshot.data.documents.length,
padding: const EdgeInsets.only(top: 2.0),
itemBuilder: (context, index) {
DocumentSnapshot ds = snapshot.data.documents[index];
return new Data(ds);
}
);
});
}
}
This is my first time working with Streams and I don't understand them quite well and I would like your thoughts on what to do.