5

Currently I have a StreamBuilder pulling data from Firebase for a group messaging app. I have a snapshot.hasData check to make sure that there is data before displaying the messages. The problem is that when setState rebuilds the widgets, it also rebuilds the StreamBuilder causing it to display the static snapshot.hasData == false content before it finishes loading the data again. This looks really bad because it loads so fast that it just looks like the screen is flickering for a second every rebuild.

How would I go about retaining the data while it reloads itself, so it does not appear to flicker?

Is there a way I can prevent the StreamBuilder from rebuilding given specific circumstances?

Thanks!

Edit added current code.

var firebaseStream;
  @override
  void initState() {
    super.initState();
    firebaseStream = Firestore.instance
        .collection('groups')
        .document(groupID)
        .collection('messages')
        .orderBy('date', descending: true)
        .limit(15)
        .snapshots();

StreamBuilder(
  stream: firebaseStream,
  builder: (context, snapshot) {
     if (!snapshot.hasData)
        return Container(
           color: Colors.red,);
        return ListView.builder(
Sam Scolari
  • 113
  • 1
  • 10

2 Answers2

5

It sounds as if you're fetching the data directly in the StreamBuilder stream property:

StreamBuilder(
  stream: firebase.getData(),
  ...
)

The problem with this is it will create a new stream every time setState is called and always give an initial null value. Instead, you should use a StatefulWidget and create a variable in your state during initState, so that it only runs once. Then use this variable as your stream property in the StreamBuilder. Here's a very simple example:

class Example extends StatefulWidget {
  Example({Key key}) : super(key: key);

  @override
  _ExampleState createState() => _ExampleState();
}

class _ExampleState extends State<Example> {
  Stream firebaseData;

  @override
  void initState() {
    super.initState();
    firebaseData = Firebase.fetchData(); //Or whatever function you use
  }

  @override
  Widget build(BuildContext context) {
    return StreamBuilder(
      stream: firebaseData,
      builder: (BuildContext context, AsyncSnapshot snapshot) {
        if (snapshot.hasData) {
          return Text(snapshot.data);
        } else {
          return Loading();
        }
      },
    );
  }
}
David
  • 1,688
  • 1
  • 11
  • 21
  • So I transfered the firebase.getData() to my initState method called firebaseStream. I put that variable into the stream property and now the messages show but as soon as the state is changed it rebuilds the StreamBuilder and now stays on the snapshot.hasData == false content indefinitely. Do you mean that I should create another statefull widget just for my stream builder and call that in my main file? – Sam Scolari Jul 21 '20 at 02:20
  • My code is layed out exactly how your example shows but now the StreamBuilder retains the snapshot.hasData == false content after the first time it is rebuilt. The StreamBuilder is a child of a few animated widgets if that changes anything. – Sam Scolari Jul 21 '20 at 02:34
  • Sorry, can you add your original code? If you're using snapshots() directly it seems you can in fact use it directly in the StreamBuilder. Unless you're doing something strange elsewhere, can't see that what I said will solve it – David Jul 21 '20 at 02:34
  • I added the relevant code to my question but here is all my code (pretty long sorry) if you need https://codeshare.io/5goxJV StreamBuilder is on line 697 initState is 115 – Sam Scolari Jul 21 '20 at 02:36
  • Ok, I checked out your code and I can't see anything obviously wrong with the logic, but seriously, split your code into widgets! Don't try to do it all in one! That might be what's causing the problem, though I don't know. I suggest you try extracting that one StreamBuilder into a page on it's own and simply displaying its output to see if it still gives problems. – David Jul 21 '20 at 02:50
  • Ok. No problem! – David Jul 21 '20 at 03:04
0

Have you tried using the snapshot.Connection state?

@override
      Widget build(BuildContext context) {
        return StreamBuilder(
          stream: firebaseData,
          builder: (BuildContext context, AsyncSnapshot snapshot) {
            if (snapshot.connectionState == ConnectionState.active && snapshot.hasData) {
final data = snapshot.data;
              return Text(data);
            } else if (snapshot.connectionState == ConnectionState.waiting) {
              return Loading(); // OR CircularProgressIndicator()
            } else // Handle error here.
          },
        );
      }
Gareth Beall
  • 293
  • 3
  • 11