I have a FireStore collection with 5 documents, and I have 2 duplicate screens (in bottom navigation bar) that listen to this collection changes with StreamBuilder.. It's a demo app for try the FireStore (See code below).
When I'm change (manually) 1 document in the FireStore so the StreamBuilder run Twice, and instead of get 10 read actions I got 20! and that can affect my firebase costs..
I see a lot of answers about this Stream behavior that run twice, but all of them is about the Initialization of the stream, and in my case is after the Initialization
How can I avoid this behavior?
First screen
class FirstScreen extends StatelessWidget {
final Stream<List<String>> chatStream = Api.getChat();
@override
Widget build(BuildContext context) {
return SafeArea(
child: StreamBuilder<List<String>>(
stream: chatStream,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return Container();
}
if (snapshot.data == null ) {
return Text('Data null');
}
return ListView.builder(
itemCount: snapshot.data.length,
itemBuilder: (context, i) {
return Text(snapshot.data[i]);
},
);
},
),
);
}
}
Second screen
class FirstScreen extends StatelessWidget {
final Stream<List<String>> chatStream = Api.getChat();
@override
Widget build(BuildContext context) {
return SafeArea(
child: StreamBuilder<List<String>>(
stream: chatStream,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return Container();
}
if (snapshot.data == null ) {
return Text('Data null');
}
return ListView.builder(
itemCount: snapshot.data.length,
itemBuilder: (context, i) {
return Text(snapshot.data[i]);
},
);
},
),
);
}
}
The listener
class Api {
static final CollectionReference users = FirebaseFirestore.instance.collection('chat');
static Stream<List<String>> getChat() {
final Stream<QuerySnapshot> result = users.snapshots();
final Stream<List<String>> chat = result.map((event) => event.docs.map((e) {
return (e.data() as Map<String, dynamic>)['text'].toString();
}).toList());
return chat;
}
}
The result in Firebase dashboard here
CodePudding user response:
StatelessWidgets get re-created every time their parents' build() is called again. It can be called again and again for various reasons.
Every time FirstScreen is re-created, Api.getChat(); is called one more time, resulting in more Firebase reads. You don't want that.
Instead you want to make these StatefulWidgets and call Api.getChat(); in initState once. That will ensure that no extra Firestore reads would happen.
class FirstScreen extends StatefulWidget {
const FirstScreen({Key? key}) : super(key: key);
@override
_FirstScreenState createState() => _FirstScreenState();
}
class _FirstScreenState extends State<FirstScreen> {
final Stream<List<String>> chatStream;
@override
void initState() {
super.initState();
chatStream = Api.getChat();
}
@override
Widget build(BuildContext context) {
return SafeArea(
child: StreamBuilder<List<String>>(
stream: chatStream,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return Container();
}
if (snapshot.data == null ) {
return Text('Data null');
}
return ListView.builder(
itemCount: snapshot.data.length,
itemBuilder: (context, i) {
return Text(snapshot.data[i]);
},
);
},
),
);
}
}
You could also keep it in State the way you had before, but it's a bit more elegant the other way. It is a reminder in case it is something that you may need to dispose of in dispose().
final Stream<List<String>> chatStream = Api.getChat();
