简体   繁体   中英

How to refresh a firebase streambuilder and reduce the number of document reads

I am working to set up a chat with Flutter and I plan to use firestore. I have a streambuilder and I am limiting the number of documents read to 20. The chat works fine and shows me the last 20 messages. Now I would like to add the next 20 older messages as soon as I scroll to the top.

I have a working solution but I am not sure it is the best one. My biggest worry is related to how many reads I am doing every time I change the value of the limit. Am I reading 20 new reads or Am I reading 40 new documents? and if I scroll more am I using 60 reads?

See below my code

void initState() {
    maxMessageToDisplay = 20;
    _scrollController = ScrollController();
    _scrollController.addListener(() {
      if (_scrollController.position.pixels == _scrollController.position.maxScrollExtent) {
        setState(() {
          maxMessageToDisplay += 20;
        });
      }
    });
    super.initState();
}

Widget build(BuildContext context) {
    return StreamBuilder<QuerySnapshot>(
      stream: _firestore.collection('chat').limit(maxMessageToDisplay).orderBy('timestamp', descending: true).snapshots(),
      builder: (context, snapshot) {
     
        final messages = snapshot.data.documents;

        messages.sort((a, b) => b.data['timestamp'].compareTo(a.data['timestamp']));
        var format = new DateFormat("Hm");

        List<MessageBubble> messageBubbles = [];
        for (var message in messages) {
          final messageText = message.data['text'];
          final messageSender = message.data['sender'];
          final messagePhoto = message.data['photo'];
          final messageUserId = message.data['uid'];

          final messageTime = format.format(DateTime.fromMillisecondsSinceEpoch(message.data['timestamp'], isUtc: false));    

          final messageBubble = MessageBubble(
            sender: messageSender,
            text: messageText,
            photo: messagePhoto,
            time: messageTime,
            userId: messageUserId,
          );
          messageBubbles.add(messageBubble);
        }
        return Expanded(
          child: ListView(
            controller: _scrollController,
            reverse: true,
            padding: EdgeInsets.symmetric(vertical: 10.0, horizontal: 10.0),
            children: messageBubbles,
          ),
        );
      },
    );
  }
}

If the listener doesn't disconnect for more than 30 minutes, you should not worry about it, because Firebase only count new reads if the document changed. So in your case if you've read 20 documents, then the user scrolls and gets 20 more, you will only have 40 and not 60 reads. You can read more about firebase billing here

You should be able to do this, by modifying the stream you are using in your stream builder.

You should create a combined stream between the two sources and push new values each time a source brings new data. for this, you can use BehaviorSubjects from the rxDart plugin (it is easy, and a bit better than standard streams, although you can do everything with standard dart streams).

this is a sample code:

List<FireStoreItem> fireStoreItems = [];

BehaviorSubject<List<FireStoreItem>> OlderItemsStream = new BehaviorSubject<List<FireStoreItem>>():

BehaviorSubject<List<FireStoreItem>> finalItemsStream = Rx.combileLatest2(
_firestore.collection('chat').limit(maxMessageToDisplay).orderBy('timestamp', descending: true).snapshots(),
OlderItemsStream, (a,b)=>a.addAll(b))

The final stream is a combination of the original stream and the older messages stream. and will generate a new snapshot everytime one of the streams gets a new value.

the last argument in Rx.combineLatest2 is the mapping of the streams where here we basically add the older messages snapshot data to the first stream's snapshot data thus creating a single list with the latest messages and the old messages and pushing it together as a new value and updating the stream builder accordingly.

your stream builder should only take the finalItemsStream as a source stream.

As for the scrolling trigger, where you need to scroll until the end to trigger the old messages to be pulled from the FireStore, you can set a listener on the scroll controller and check for atEdge bool that will tell you if you reached the scrolling edge or not.

here is a sample code:

scrollController.addListener(() {
      if(scrollController.position.atEdge){
        //create a fireStore call and add the resulting data to the olderItems stream
        FireStoreCall().then((data){
          OlderItemsStream.add(oldMessages);
        });
      }
    });

NOTE: The scrollController should be the same used in the listView

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM