25

Hello I'm trying to listen state of bloc form other bloc. I'm using this package https://pub.dev/packages/bloc

From my UserBloc I want listen AuthBloc and when It has the state AuthenticationAuthenticated the UserBloc should fire an event.

final UserRepository userRepository;
final authBloc;
StreamSubscription authSub;
UserBloc({ @required this.userRepository, @required this.authBloc}) {
    authSub = authBloc.listen((stateAuth) {

      //here is my problem because stateAuth, even is AuthenticationAuthenticated it return always false.
      if (stateAuth is AuthenticationAuthenticated) {
        this.add(GetUser())  ;
      }
    });
  }

@override
  Future<void> close() async {
    authSub?.cancel();
    super.close();
  }

For now I have this problem: When in debug I'm trying to print stateAuth it return:

stateAuth = {AuthenticationAuthenticated} AuthenticationAuthenticated
   props = {_ImmutableList} size = 0

But stateAuth is AuthenticationAuthenticated return always false.

Is there any way for listen blocState From Other Bloc class?

tomerpacific
  • 4,704
  • 13
  • 34
  • 52
LorenzoBerti
  • 6,704
  • 8
  • 47
  • 89

5 Answers5

21

Actually in one of the examples of the bloc library they listen to a Bloc (TodosBloc) from another Bloc (FilteredTodosBloc).

class FilteredTodosBloc extends Bloc<FilteredTodosEvent, FilteredTodosState> {
  final TodosBloc todosBloc;
  StreamSubscription todosSubscription;

  FilteredTodosBloc({@required this.todosBloc}) {
    todosSubscription = todosBloc.listen((state) {
      if (state is TodosLoadSuccess) {
        add(TodosUpdated((todosBloc.state as TodosLoadSuccess).todos));
      }
    });
  }
...

You can check this example's explanation here.

georkings
  • 616
  • 8
  • 8
  • 7
    The article is marked as out of date, the latest doesn't use this approach anymore, don't know if it is still appropriate. – thanhbinh84 Apr 07 '22 at 08:08
19

To answer Sampir's question, yes, you're right, but sometimes you may want to do it in another way. A bloc is something that manages an event for someone else. If you are working with ui events, your bloc manages them for your ui, but if you are working also with other kind of events (i.e. position events, or other streams events) you can have a bloc that manages your ui events and antoher bloc that manages the other kind of events (i.e. a bluetooth connection). So the first bloc must listen to the second one (i.e. because is waiting for establishing bluetooth connection). Think about an app that uses a lot of sensors, each one with its stream of data, and you'll have a chain of blocs that have to cooperate. You can do it with multi-provider and multi-listener but your chain could be very long and writing your listener cases can be hard, or you may want to hide it from your ui, or you want to reuse it in another part of your app, so you may want to build your chain inside your blocs.

You can add a listener to a bloc almost everywhere. Using StreamSubscription, you can add a listener to every kind of streams, even the one in another bloc. The bloc must have a method to expose his stream, so you can listen to him.

Some code (I use flutter_bloc - flutter_bloc has multi-providers, but it's just for example):

class BlocA extends Bloc<EventA, StateA> {

  final BlocB blocB;
  StreamSubscription subscription;

  BlocA({this.blocB}) {
    if (blocB == null) return;
    subscription = blocB.listen((stateB) {
      //here logic based on different children of StateB
    });
  }

  //...

}

class BlocB extends Bloc<EventB, StateB> {
   //here BlocB logic and activities
}
user3042236
  • 279
  • 3
  • 5
  • I have question i'm doing same what u mention about Bluetooth connection but for WiFi and cellular i'm using block i want this block to listen for change happen to connectivity and rebuild the UI but in this way i had only one block use the connection class , but to trigger build i had to use event from UI. what i want to do is to trigger the event from background thread that watch connection change. – UN..D Jun 22 '20 at 09:33
  • Please note that listening to other blocs inside of a bloc is discouraged, because it creates tight coupling between them. You can avoid it by wrapping the BlocProvider of BlocA with a BlocListener for BlocB and adding events to BlocA for every event of BlocB. See https://bloclibrary.dev/#/architecture?id=bloc-to-bloc-communication – stefanS May 10 '23 at 10:53
9

You might not want your bloc to depend on another bloc with direct bloc-to-bloc dependency, instead, you might want to connect your bloc through the presentation layer.

You can use a BlocListener to listen to one bloc and add an event to another bloc whenever the first bloc changes.

class MyWidget extends StatelessWidget {
  const MyWidget({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return BlocListener<WeatherCubit, WeatherState>(
      listener: (context, state) {
        // When the first bloc's state changes, this will be called.
        //
        // Now we can add an event to the second bloc without it having
        // to know about the first bloc.
        BlocProvider.of<SecondBloc>(context).add(SecondBlocEvent());
      },
      child: TextButton(
        child: const Text('Hello'),
        onPressed: () {
          BlocProvider.of<FirstBloc>(context).add(FirstBlocEvent());
        },
      ),
    );
  }
}

The code above prevents SecondBloc from needing to know about FirstBloc, encouraging loose-coupling.

Check the official documentation for more info.

genericUser
  • 4,417
  • 1
  • 28
  • 73
Godwin Mathias
  • 326
  • 4
  • 12
7

A recent update in the bloc source code requires a small change to the solution.

You now have to listen to a bloc/cubit's stream attribute, please see below example.

class FilteredTodosBloc extends Bloc<FilteredTodosEvent, FilteredTodosState> {
  final TodosBloc todosBloc;
  StreamSubscription todosSubscription;

  FilteredTodosBloc({@required this.todosBloc}) {
    todosSubscription = todosBloc.stream.listen((state) {
    //                             ^^^^^
      if (state is TodosLoadSuccess) {
        add(TodosUpdated((todosBloc.state as TodosLoadSuccess).todos));
      }
    });
  }
...
Vingtoft
  • 13,368
  • 23
  • 86
  • 135
0

The answers that suggest passing a bloc to another bloc are not that great imo, as they will create tight coupling between the 2 blocs.

I think the answers that suggest passing the state of the first bloc to the dependent bloc are better, and they can be imporved by this approach below where you really decouple the blocs from each other using a stream of custom class/record that will be passed to the dependent bloc, this way you can decouple the blocs and also listen only to the part of the first bloc's state that you are interested in, in the second bloc:

class PartialState{ // partial state of bloc B that bloc A is interested in (it can be also the whole state, in which case you pass below the state stream directly instead of a stream of this class)
  final SomeParamFromBlocBState param1;

  final AnotherParamFromBlocBState? param2;

  // constructor...
}

class BlocA extends Bloc<EventA, StateA> {

  final PartialState partialState;
  StreamSubscription<PartialState> subscription;

  BlocA(Stream<PartialState> stream) {
    subscription = stream.listen((partialState) {
      // do something
    });
  }

//...

}

class BlocB extends Bloc<EventB, StateB> {
  //here BlocB logic and activities
}

// somewhere in your app
void main(){
  final blocB = BlocB();
  final blocA = BlocA(blocB.state.map((state) => PartialState(param1: state.param1, param2: state.param2)));
}

notes:

  • Don't forget to close the subscription in the bloc's close method.
  • Be careful when listening to the state of a bloc from another bloc, because if you are defining your blocs in some build method and the bloc that is providing the stream rebuilds while the depending bloc doesn't build, the depending bloc may have broken state as the stream it is listening to is no longer valid.
  • Using BlocListener is also ok sometimes,other times its not, example: say you want to listen to some state of BlocB and call onBlocBChanged method defined in BlocA, if you are going to do this once in your widgets, then its ok, but if you need to call this in many places, you may forget to call it somewhere (i.e. you may forget to put a bloc listener), in this case a better approach is to centralize the call by using the above approach.
HII
  • 3,420
  • 1
  • 14
  • 35