StreamBuilder: Don't rebuild at each page change in PageView

Viewed 22

I'm working with StreamBuilder to retrieve real time data from Firestore, showing a number in the actions section of the AppBar enter image description here

Every time I change page of PageView with BottomNavigationBar the white Button is rebuilt, causing a bad flashing effect, with numbers disappearing and appearing and also with a reduction and increasing of the size.
How could I avoid this behaviour? I'd like the Button not to be rebuilt at each page change.

CODE:

actions: [
  Padding(
      padding: const EdgeInsets.all(14),
      child: ElevatedButton(
        style: ButtonStyle(
    
            backgroundColor:
            MaterialStateProperty.all<Color>(Colors.white),
            shape:
            MaterialStateProperty.all<RoundedRectangleBorder>(
                RoundedRectangleBorder(
                    borderRadius: BorderRadius.circular(8),
                    side:
                    const BorderSide(color: Colors.grey)))),
        onPressed: () {
    },
        child: Padding(
          padding: const EdgeInsets.fromLTRB(5, 5, 5, 5),
          child: Row(
            children: [
              Padding(
                padding: const EdgeInsets.only(right: 10),
                child: ConstrainedBox(
                  constraints: const BoxConstraints(
                      maxWidth: 17, maxHeight: 17),
                  child: svg,
                ),
              ),
              StreamBuilder<DocumentSnapshot<Map<String, dynamic>>>(
                stream: FirebaseFirestore.instance
                      .collection('Users')
                      .doc(documentId)
                      .snapshots(),
                  builder: (BuildContext context,
                      AsyncSnapshot<DocumentSnapshot<Map<String, dynamic>>>
                      snapshot) {
                    if (snapshot.hasError) {
                      return const Text('error');
                    }
    
                    if (snapshot.connectionState == ConnectionState.waiting) {
                      return const Text('');
                    }
    
                    Map<String, dynamic> data =
                    snapshot.data!.data() as Map<String, dynamic>;
    
                  return Text(
                      formatter.format(data['balance']),
                    style:  TextStyle(
                      fontWeight: FontWeight.bold,
                      color: Colors.black87,
                      fontSize: data['balance'] > 10000000 ? 12 : 14,
                    ),
                  );
                }
              ),
            ],
          ),
        ),
      ),
    ),
],
1 Answers

You're passing the stream to the StreamBuilder like this:

StreamBuilder<DocumentSnapshot<Map<String, dynamic>>>(
  stream: FirebaseFirestore.instance
        .collection('Users')
        .doc(documentId)
        .snapshots(),

This means that every time the UI is updated, you're creating a new connection to Firestore to load the user's document.

To prevent this from happening:

  1. Put the snapshots stream into a the stat of a stateful object
  2. Use it from there in your StreamBuilder's stream property

With the stream being part of the widget's state, it will only be created when a new state is needed - so when the screen is first rendered, not each time it updates.

This video from Randal Schwartz also shows this problem and how to fix it, so I recommend watching Fixing a common FutureBuilder and StreamBuilder problem.

Related