Home > Net >  StreamBuilder call Firestore twice after initialization
StreamBuilder call Firestore twice after initialization

Time:01-31

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();
  •  Tags:  
  • Related