Open lirantzairi opened 4 months ago
Hi @lirantzairi 👋 Thanks for opening an issue!
It’s hard to say without a link to a sample app which illustrates the setup and problem you’re facing but it sounds like you should be able to notify the bloc (add an event) when navigating away (then the bloc can pause the subscription) and similarly notify the bloc when you navigate back to the page (then the bloc can resume the subscription).
Let me know if that helps. If you’re still facing issues, then please share a link to a minimal reproduction sample and I’m happy to take a closer look 👍
Thanks for the response @felangel 🙂
Think that CounterRepository can listen to a Firebase document, so to reduce costs we want to have it on only when necessary. Now, when you launch the app and you're in HomeScreen, the counter isn't running because the bloc is lazy (that's good). It starts running only when you go to CounterScreen. The problem is that when you go back to home it will continue running. I want to be able to cancel _streamSubscription when navigating back to HomeScreen and set it again when navigating again to CounterScreen.
Technically you're right that it's possible to notify the bloc when navigating away and back. But this will cause the presentation layer to send events to the bloc layer just for the purpose of the bloc maintaining itself. Which in my opinion contradicts one of the purposes of this architecture. From your documentation:
The presentation layer’s responsibility is to figure out how to render itself based on one or more bloc states. In addition, it should handle user input and application lifecycle events.
It makes more sense that the bloc has its own API to notify when its stream is (or is stopped) being listened to. Hope I managed to make more sense.
It makes more sense that the bloc has its own API to notify when its stream is (or is stopped) being listened to. Hope I managed to make more sense.
The issue with this is that one Bloc can be used in multiple places across the application. Adding an event about such case (that you wanna stop smt) is the correct universal approach.
Also note that it's not necessary true that Blocs are part of presentation layer. They are more in between presentation and application, or however you wanna call them. Bloc don't even know anything about BuildContext etc., therefore it cannot listen to platform updates. The thing that brings Bloc to presentation is BlocProvider/BlocBuilder, but Bloc itself is unaware.
Thanks for the response @felangel 🙂
I created this example, tried to simplify as much as possible:
import 'dart:async'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; class CounterRepository { const CounterRepository(); Stream<int> streamCount() { return Stream.periodic(const Duration(seconds: 1), (count) => count); } } class CounterCubit extends Cubit<int> { final CounterRepository repository; StreamSubscription<int>? _streamSubscription; CounterCubit({ required this.repository, }) : super(0) { _streamSubscription = repository.streamCount().listen((count) { emit(count); }); } @override Future<void> close() { _streamSubscription?.cancel(); return super.close(); } } class CounterScreen extends StatelessWidget { const CounterScreen(); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: Text(runtimeType.toString())), body: Center( child: BlocBuilder<CounterCubit, int>( builder: (context, count) { return Text(count.toString()); }, ), ), ); } } class HomeScreen extends StatelessWidget { const HomeScreen(); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: Text(runtimeType.toString())), body: Center( child: TextButton( onPressed: () { Navigator.of(context).push( MaterialPageRoute(builder: (_) => const CounterScreen()), ); }, child: const Text('Go to CounterScreen'), ), ), ); } } void main() { runApp( MaterialApp( builder: (context, child) { return RepositoryProvider( create: (_) => const CounterRepository(), child: BlocProvider( create: (context) => CounterCubit( repository: context.read<CounterRepository>(), ), child: child, ), ); }, home: const HomeScreen(), ), ); }
Think that CounterRepository can listen to a Firebase document, so to reduce costs we want to have it on only when necessary. Now, when you launch the app and you're in HomeScreen, the counter isn't running because the bloc is lazy (that's good). It starts running only when you go to CounterScreen. The problem is that when you go back to home it will continue running. I want to be able to cancel _streamSubscription when navigating back to HomeScreen and set it again when navigating again to CounterScreen.
Technically you're right that it's possible to notify the bloc when navigating away and back. But this will cause the presentation layer to send events to the bloc layer just for the purpose of the bloc maintaining itself. Which in my opinion contradicts one of the purposes of this architecture. From your documentation:
The presentation layer’s responsibility is to figure out how to render itself based on one or more bloc states. In addition, it should handle user input and application lifecycle events.
It makes more sense that the bloc has its own API to notify when its stream is (or is stopped) being listened to. Hope I managed to make more sense.
In this case, I'd recommend moving the BlocProvider
lower in the widget tree (just scoped to the CounterScreen
. That way when the CounterScreen
is unmounted, the bloc will be closed and the underlying subscription will also be canceled. Hope that helps 👍
Description
In my Bloc I have a stream subscription which listens to a Firebase Firestore's document real time updates. I want to maintain the state even when the user moves to a different screen in the app so I keep the Bloc alive. However when the user is in a different screen I want to pause the Firestore listener so I need to know once the relevant BlocBuilder is disposed of. Also I need to know once any new BlocBuilder is listening again to this Bloc so I can resume the Firestore listener.
Desired Solution
Add two overridable methods to BlocBase:
onStreamListen
will be invoked once the first Bloc widget (BlocBuilder, BlocSelector, BlocConsumer etc) is attached.onStreamCancel
will be invoked once all the Bloc widgets attached to this Bloc are disposed of.An intuitive solution would be to assign these functions to the creation of
late final _stateController = StreamController<State>.broadcast();
But it's problematic when using BlocProvider because this provider also attaches a listener of its own to the Bloc.