1

I have an application with multiple indexes on a navigation view, when I first load the app, as expected the StreamBuilder is called two times. however, when I navigate to another index and back the build with the stream builder seems to be calling the stream builder to redownload all of the items inside of it. I am worried this is going to consume all of my firebase data if this loop is ran for too long of a period. How could I restructure to stop the StreamBuilder from being called many times when switching pages. Note, I even save the state of this index when I switch and come back.

My build method:

@override
  Widget build(BuildContext context) {
    return StreamBuilder<QuerySnapshot>(
        stream: Firestore.instance
            .collection("posts/player/post")
            .orderBy("time", descending: true)
            .snapshots()
            .take(2),
        builder: (BuildContext context, AsyncSnapshot<QuerySnapshot> snapshot) {
          if (!snapshot.hasData) return const Text('Loading...');
          final int highLightCount = snapshot.data.documents.length;
          return new StreamBuilder<QuerySnapshot>(
              stream: Firestore.instance
                  .collection("posts/player/post")
                  .snapshots(),
              builder: (BuildContext context,
                  AsyncSnapshot<QuerySnapshot> snapshot) {
                if (!snapshot.hasData)
                  return new Text("There are no current posts");
                return new ListView(
                  physics: const AlwaysScrollableScrollPhysics(),
                  scrollDirection: Axis.vertical,
                  shrinkWrap: true,
                  children: getPostItems(snapshot),
                );
              });
        });
  }

When I switch it is calling the getPostItem(snaphot) that is being called nearly nonstop, how could I prevent this from being called and consuming my data, or, prevent the StreamBuilder from changing unless something actually updates?

The rest of the important code:

getPostItems(AsyncSnapshot<QuerySnapshot> snapshot) {
    return snapshot.data.documents.map((doc) => getListItem(doc)).toList();
  }

  Widget getListItem(var doc) {
    getProfUrl(doc);
    getDownUrl(doc);

    print("item hit");

    print("user: " + doc["user"] + "- current: " + widget.auth.getUserId());
    if (doc["user"] != widget.auth.getUserId()) {
      print("will show");
      if (doc["type"] == "image") {
        return new Column(
          children: <Widget>[
            new ListTile(
                title: new Text(doc["title"]),
                subtitle: new Text(doc["description"].toString()),
                leading: new Container(
                    width: 44.0,
                    height: 44.0,
                    decoration: new BoxDecoration(
                      shape: BoxShape.circle,
                      image: new DecorationImage(
                          fit: BoxFit.fill, image: NetworkImage(profUrl)),
                    ))),
            new Padding(
              padding: EdgeInsets.fromLTRB(4, 4, 4, 4),
              child: new Center(
                child: new AspectRatio(
                  aspectRatio: 1 / 1,
                  child: new Container(
                    decoration: new BoxDecoration(
                        image: new DecorationImage(
                      fit: BoxFit.fill,
                      alignment: FractionalOffset.topCenter,
                      image: new NetworkImage(downUrl),
                    )),
                  ),
                ),
              ),
            ),
          ],
        );
      } else {
        VideoPlayerController _controller = VideoPlayerController.network(
            'http://www.sample-videos.com/video123/mp4/720/big_buck_bunny_720p_20mb.mp4')
          ..initialize().then((_) {
            // Ensure the first frame is shown after the video is initialized, even before the play button has been pressed.
            setState(() {});
          });

        return new Column(children: <Widget>[
          new ListTile(
              title: new Text(doc["title"]),
              subtitle: new Text(doc["description"].toString()),
              leading: new Container(
                  width: 44.0,
                  height: 44.0,
                  decoration: new BoxDecoration(
                    shape: BoxShape.circle,
                    image: new DecorationImage(
                        fit: BoxFit.fill, image: NetworkImage(profUrl)),
                  ))),
          new Padding(
            padding: EdgeInsets.fromLTRB(4, 4, 4, 4),
            child: new Center(
              child: new AspectRatio(
                aspectRatio: 500 / 500,
                child: VideoPlayer(_controller),
              ),
            ),
          ),
        ]);
      }
    }
  }
tyler powell
  • 130
  • 1
  • 12

1 Answers1

4

In order to reduce the number of times that the Stream is being created, you could move its initialisation into the initState method of the State class.

class MyPage extends StatefulWidget {
  @override
  _MyPageState createState() => _MyPageState();
}

class _MyPageState extends State<MyPage> {
  Stream<QuerySnapshot> _outerStream;
  Stream<QuerySnapshot> _innerStream;

  @override
  Widget build(BuildContext context) {
    return StreamBuilder<QuerySnapshot>(
      stream: _outerStream,
      builder: (context, snapshot) {
        if (!snapshot.hasData) return const Text('Loading...');
        final int highLightCount = snapshot.data.documents.length;
        return StreamBuilder<QuerySnapshot>(
          stream: _innerStream,
          builder: (context, snapshot) {
            if (!snapshot.hasData) return Text('There are no current posts');
            return ListView(
              physics: const AlwaysScrollableScrollPhysics(),
              scrollDirection: Axis.vertical,
              shrinkWrap: true,
              children: getPostItems(snapshot),
            );
          },
        );
      },
    );
  }

  @override
  void initState() {
    super.initState();

    _outerStream = Firestore
        .instance
        .collection('posts/player/post')
        .orderBy('time', descending: true)
        .snapshots()
        .take(2);

    _innerStream = Firestore
        .instance
        .collection('posts/player/post')
        .snapshots();
  }
}

Alternatively, if you would like to create the Stream once during the lifetime of the application, then you could use a StreamProvider to provide the Stream.

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MultiProvider(
      providers: [
        StreamProvider<QuerySnapshot>(
          create: (context) {
            return Firestore.instance.collection('posts/player/post').snapshots();
          },
        ),
      ],
      child: MaterialApp(
        home: MyPage(),
      ),
    );
  }
}

class MyPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final snapshot = context.watch<QuerySnapshot>();

    return Scaffold(
      body: snapshot == null
          ? Text('There are no current posts')
          : ListView(
              physics: const AlwaysScrollableScrollPhysics(),
              scrollDirection: Axis.vertical,
              shrinkWrap: true,
              children: getPostItems(snapshot),
            ),
    );
  }
}
tnc1997
  • 1,832
  • 19
  • 20
  • This was an awesome response! I've been struggling a lot with this when using FutureBuilder's and StreamBuilders, the example code was awesome too! – tyler powell Jun 28 '20 at 18:08
  • If you have a moment, could you share with me why you have the inner and the outer stream? It appears as if one checks if they exist and the second displays, is that correct? – tyler powell Jun 29 '20 at 02:23
  • 1
    Glad the code samples were useful, happy to help! I noticed in your code sample that you had two different streams. I simply named the properties outer and inner to highlight them, so feel free to rename them to something more meaningful. You are correct, the outer stream is used in the `Text('Loading...')` and the `final int highLightCount`, whilst the inner stream is used in the `Text('There are no current posts')` and the `ListView(...)`. – tnc1997 Jun 29 '20 at 06:50
  • Peeerfeeeeeccctttt!!!!!!!!..... I was stuck here for dayyyss!... This code helped a loooott!! – blessing dickson Dec 17 '20 at 20:21