Search code examples
flutterfirebasegoogle-cloud-firestorestreamreal-time

flutter firebase: streams interfering with one another


Edit: Ive debugged a bit more, so im going to update this post with what I believe to be the current problem.

The situation is the following:

    1. User enters chat B. Chat A is updated. User will stay in chat 
       B.
    2. User enters chat A, leaves, then enters chat B. Chat a is 
       updated. User is swapped into chat A.

This leads me to think that the problem is that the app continues listening to an old stream, and if that stream is updated, the user is thrown back into the unwanted chat. With that in mind:

Im listening to a stream using a bloc. When a user clicks on a chat, the following function is called:

Provider.of<MessageBloc>(context, listen: false).add(LoadMessage(
        chat.stingrayId!, chat.chatId, chat.matchedUserImageUrl!,
        chat: chat,
        stingrayIndex: stingrayIndex,
        matchedUserId: chat.matchedUserId!));

which leads to:

void _onLoadMessage(
    LoadMessage event,
    Emitter<MessageState> emit,
  ) {
    _firestoreRepository
        .messages(event.stingrayId, event.messageId)
        .listen((messages) {
      _firestoreRepository.getUser(event.matchedUserId).listen((matchedUser) {
        add(
          UpdateMessage(
              messages: messages.messages,
              matchedUserImageUrls: event.matchedUserImageUrls,
              chat: event.chat,
              matchedUser: matchedUser,
              blocked: messages.blocked,
              blockerName: messages.blockerName,
              blockerId: messages.blockerId,
              stingrayIndex: event.stingrayIndex),
        );
      });
    });
  }

and the stream it listens to is:

Stream<MessagesWithBlock> messages(String stingrayId, String messageId) {
    return FirebaseFirestore.instance
        .collection('stingrayMessages')
        .doc(messageId)
        .snapshots()
        .map<MessagesWithBlock>((doc) => MessagesWithBlock.fromSnapshot(doc));
  }

Which is updated by:

void _onUpdateMessage(
    UpdateMessage event,
    Emitter<MessageState> emit,
  ) {
    List<Message?> messages = event.messages;
    messages.sort((b, a) => a!.dateTime.compareTo(b!.dateTime));

    emit(MessageLoaded(
        messages: messages,
        matchedUserImageUrl: event.matchedUserImageUrls,
        chat: event.chat,
        matchedUser: event.matchedUser,
        blocked: event.blocked,
        blockerName: event.blockerName,
        blockerId: event.blockerId,
        stingrayIndex: event.stingrayIndex));
  }

Is there a way to cancel a bloc's stream stubscription, in this case the messages and user subscription, when a user leaves a screen?

Thanks!


Solution

  • This is a common mistake when working with streams - forgetting to close the stream (or cancel a listener) when it's no longer needed.

    In your widget, you need to keep a reference to the listener (presumably some kind of StreamSubscription) and then once the user has left that page, cancel the listener in the widget's dispose function:

    late StreamSubscription _listener;
    
    void _onLoadMessage(
        LoadMessage event,
        Emitter<MessageState> emit,
      ) {
        _listener = _firestoreRepository
          .messages(event.stingrayId, event.messageId)
          .listen((messages) {
        ...
        });
    }
    
    @override
    void dispose() {
        _listener.cancel();
        super.dispose();
    }