StreamBuilder triggering multiple reads from firestore

I’m trying to read data from firestore and then display them as a card. At first everything is good but once I swipe or touch the card the callback function triggers again and I get multiple reads. I even tried to put the stream in the InitState() but still the same.

              child: Column(
                children: [
                  StreamBuilder<QuerySnapshot>(
                    stream: _firestore
                        .collection('Posts')
                        .limit(3)
                        .where('seen', isEqualTo: 0)
                        .snapshots(),
                    builder: (context, snapshot) {
                      if (!snapshot.hasData) return LinearProgressIndicator();

                      final posts = snapshot.data.documents;
                      for (var post in posts) {
                        final imgPath = post.data['imgPath'];
                        welcomeImages.add(imgPath);
                      }
                      print(welcomeImages);
                      return Image.network(
                        '${welcomeImages[index]}',
                        fit: BoxFit.cover,
                      );
                    },
                  ),
                  Text('s'),
                ],
              ),
            ),

Hey have you tried extracting the stream out, calling SetState when a new stream is received and disposing the stream when the widget is rebuild?

Maybe that solves the problem.

1 Like

Can you give me an example of doing that please? would appreciate that mate.
I tried calling the stream from InitState() but still, multiple reads. This is what I’ve done.

   void initState() {
   msgsStream = chatService.chatData(widget.chatRoomId);
   super.initState();
   }
StreamBuilder<List<ChatItemModel>>(
            stream: msgsStream,
            builder: (context, snapshot) {
              if (!snapshot.hasData) {
                return Row(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  mainAxisAlignment: MainAxisAlignment.center,
                  children: [
                    Text(
                      'Start conversation by typing something below',
                      style: TextStyle(
                        color: Colors.grey,
                      ),
                    )
                  ],
                );
              }

              final chatData = snapshot.data;
              for (var chatD in chatData) {
                message = chatD.message;
                chatRoomData.add(ChatItemModel(message: message));
              }
              return Expanded(
                child: ListView.builder(
                  itemCount: snapshot.data.length,
                  reverse: true,
                  itemBuilder: (context, index) {

I’ll do it today and get back to you :wink:

1 Like

Hai @F.lucadetena, Still waiting for your response mate, just notifying you in case you forgot.

Sorry man i had some crazy days. I can’t reproduce what you are doing with the code you’ve provided. But I’m currently implementing some things with Firestore as a Stream, I can share how I do it if you want, or you can paste the full code of what you are doing and I’ll try it out.

1 Like

Hey @cAriek did you solve the problem??

1 Like

Yes sir, things got better. However, if Its possible can you throw me a simple example of extracting the stream out? Kinda searched for it but didn’t find it.
Take your time tho, I’m not in a hurry!

Cheers mate!

1 Like

Tomorrow I’ll add the code here :wink: Nice to see you solve the problem.

1 Like

Hey @cAriek I’m so sorry I never fulfilled my promise. Here’s the code to use external Streams. I normally use them unless I’m using a StreamProvider.

import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutter/material.dart';

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

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

class _StreamState extends State<Stream> {
  List<String> _messages = [];
  StreamSubscription _messSubs;

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

  @override
  void dispose() {
    _messSubs.cancel();
    super.dispose();
  }

  void _getMessages() {
    final FirebaseFirestore _db = FirebaseFirestore.instance;
   _messSubs  =  _db.collection('messages').snapshots().listen((_mess) {
      _messages = _mess.docs as List<String>;
      setState(() {});
    });
  }

  @override
  Widget build(BuildContext context) {
    return ListView.builder(
      itemCount: _messages.length,
      itemBuilder: (BuildContext context, int idx) => Text(_messages[idx]),
    );
  }
}

Normally I map all the results I get from Firestore to a class so I can use intellisence an be sure that I’m not getting any null values. You could do this like:

import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutter/material.dart';

class Mess {
  final String id;
  final String text;
  final Timestamp created;

  Mess({@required this.id, @required this.text, @required this.created});

  factory Mess.fromMap(Map data) => Mess(
        id: data['id'] ?? '',
        text: data['text'] ?? '',
        created: data['created'] ?? Timestamp.now(),
      );
}

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

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

class _StreamState extends State<Stream> {
  List<Mess> _messages = [];
  StreamSubscription _messSubs;

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

  @override
  void dispose() {
    _messSubs.cancel();
    super.dispose();
  }

  void _getMessages() {
    final FirebaseFirestore _db = FirebaseFirestore.instance;
   _messSubs =  _db.collection('messages').snapshots().listen((_mess) {
      _messages = _mess.docs.map((doc) => Mess.fromMap(doc.data()));
      setState(() {});
    });
  }

  @override
  Widget build(BuildContext context) {
    return ListView.builder(
      itemCount: _messages.length,
      itemBuilder: (BuildContext context, int idx) => Column(
        children: [
          Text('ID OF THE MESS: ${_messages[idx].id}'),
          Text(_messages[idx].text),
          Text('Sent: ${_messages[idx].created}'),
        ],
      ),
    );
  }
}

I learned this last thing, and even a more general way of doing it if you are calling multiple collections and docs with different classes, as any medium/big app would do, from this guy:

Jeff Delaney: Best Firebase/ Angular /Flutter tutorials, and his Full Flutter course is really worth it. I’m a pro member of him, but subscribing for a month is more than enough time to do his course and some other he has on modeling Firestore.
His youtube channel
His Courses Website

1 Like