Closed wujek-srujek closed 13 hours ago
I would also like to describe our use case (CC @stev3nsen): we were trying to use the Connecting Blocs through Presentation approach, but encountered some problems with it.
The use case looks as follows (simplified): On one page of our app (page 1) we have a list of entries. The same entries are displayed on a map (Google map) on a separate page (page 2).
Now we implemented a filter mechanism which allows to filter the entries by categories. The filter mechanism appears in both pages. The state of the filter is held in a bloc FilterBloc
. The list's state is held in the ListBloc
, the map's state is held in the MapBloc
.
Both pages should use the same filter state. I.e. if I select "filter 1" on page 1, then also page 2 should apply "filter 1" automatically. The FilterBloc
is already used on both pages, thus the state of the filter bar itself is already synchronised between them.
However, the single states of ListBloc
and MapBloc
still need to be updated, once the filters in the FilterBloc
change.
Therefore, what we do in the two pages' build
functions is the following:
List page:
BlocListener<FilterBloc, FilterState>(
listener: (context, state) =>
context.read<ListBloc>().add(ListFilterChanged(state.filter)),
child: BlocBuilder<ListBloc, ListState>(
builder: (context, state) {
// build list with filtered items
},
),
)
Map page:
BlocListener<FilterBloc, FilterState>(
listener: (context, state) =>
context.read<MapBloc>().add(MapFilterChanged(state.filter)),
child: BlocBuilder<MapBloc, MapState>(
builder: (context, state) {
// build map with filtered marker items
},
),
)
Which is exactly like in the example from the docs.
The problem we encounter here is that the synchronisation of the Blocs now depends on the UI.
E.g. if we are on the list page and change the filters, the map page is not active and thus the state of the MapBloc
will not get updated accordingly and will be inconsistent. When we later switch to the map, it's items are incorrect.
The same happens the other way round: if we are currently on the map page, modify some filter and then switch to the list page, the filter state there will be different (wrong).
We ended up sticking to an approach similar to the one mentioned by @wujek-srujek, which connects the Blocs directly, without the involvement of the UI.
Found this article which explains connection via domain on specific example: https://medium.com/flutter-community/blocs-with-reactive-repository-5fd440d3b1dc might be helpful
I would also like to describe our use case (CC @Stev3nsen): we were trying to use the Connecting Blocs through Presentation approach, but encountered some problems with it.
The use case looks as follows (simplified): On one page of our app (page 1) we have a list of entries. The same entries are displayed on a map (Google map) on a separate page (page 2).
Now we implemented a filter mechanism which allows to filter the entries by categories. The filter mechanism appears in both pages. The state of the filter is held in a bloc
FilterBloc
. The list's state is held in theListBloc
, the map's state is held in theMapBloc
.Both pages should use the same filter state. I.e. if I select "filter 1" on page 1, then also page 2 should apply "filter 1" automatically. The
FilterBloc
is already used on both pages, thus the state of the filter bar itself is already synchronised between them. However, the single states ofListBloc
andMapBloc
still need to be updated, once the filters in theFilterBloc
change. Therefore, what we do in the two pages'build
functions is the following: List page:BlocListener<FilterBloc, FilterState>( listener: (context, state) => context.read<ListBloc>().add(ListFilterChanged(state.filter)), child: BlocBuilder<ListBloc, ListState>( builder: (context, state) { // build list with filtered items }, ), )
Map page:
BlocListener<FilterBloc, FilterState>( listener: (context, state) => context.read<MapBloc>().add(MapFilterChanged(state.filter)), child: BlocBuilder<MapBloc, MapState>( builder: (context, state) { // build map with filtered marker items }, ), )
Which is exactly like in the example from the docs.
The problem we encounter here is that the synchronisation of the Blocs now depends on the UI. E.g. if we are on the list page and change the filters, the map page is not active and thus the state of the
MapBloc
will not get updated accordingly and will be inconsistent. When we later switch to the map, it's items are incorrect. The same happens the other way round: if we are currently on the map page, modify some filter and then switch to the list page, the filter state there will be different (wrong).We ended up sticking to an approach similar to the one mentioned by @wujek-srujek, which connects the Blocs directly, without the involvement of the UI.
Is there a reason you couldn't use a ItemsBloc
instead that is providing the items to the map UI and the list UI and have the bloc provided further up the widget tree to be available to both UI?
Hey there ! I just saw a section in the docs regarding this. Does that mean we can close this?
https://bloclibrary.dev/architecture/#bloc-to-bloc-communication
Hey there ! I just saw a section in the docs regarding this. Does that mean we can close this?
https://bloclibrary.dev/architecture/#bloc-to-bloc-communication
Sure yeah this issue is fairly old. If the docs are insufficient let us know and I’m more than happy to elaborate.
I actually think that the questions in this issue (or in the other issues that were marked as duplicates) where not answered.
Especially this one:
You essentially officially recommend against Bloc-to-Bloc communication, and I would consider the main reason controversial:
Generally, sibling dependencies between two entities in the same architectural layer should be avoided at all costs
Where does this rule come from? If you take a look at e.g. onion or port-adapters (hexagonal) architectures, nowhere do they prohibit components in the same layer ('siblings') from talking to each other. The only 'bad'/wrong dependencies are pointing 'outside' (e.g. a repository knowing a Bloc). I have a lot of backend experience and it is also day-to-day business to call a component with business logic by another component in the same layer.
I have built a few large apps with bloc (actually cubits) now and tried different approaches. If you go for the "cubits should not reference other cubits" approach, it won't work unless you have repositories referencing repositories. Or you invent a new domain class type ("Manager", "Service", "Usecase",...) which can reference each other.
Please don't close this. We would like to hear why this rule is in place.
As you mentioned here, we have been waiting to hear your thoughts https://github.com/felangel/bloc/issues/4002#issuecomment-2037894674
I also would like to get an answer before this issue is closed.
For me, and it seems for many others, bloc communication feels like a core part of the business logic, so the UI layer shouldn’t be handling it. Especially in cases where one bloc needs to respond to another’s state changes.
Additionally, marking emit
as internal and restricting state emissions to the Emitter
within the on
directive feels a bit limiting. Both decisions seem to enforce an opinionated approach that impacts modularity. Even if we wanted to go against the recommended approach, the emit
restriction blocks us, forcing us to disable the lint rule on a case-by-case basis.
I’d really like to understand the reasoning behind these choices.
Hi Felix (@felangel).
First off, I'm sorry if this is not the right place for discussion, it used to work fine here but I've been away for a long time so please forgive me.
After a many months hiatus, I'm back to Flutter with a vengeance, and back to my favourite state management library. I decided to see what changed and was very surprised to read the Bloc-to-Bloc Communication section. It seems to be making statements that many people (at least in my surroundings) don't agree with, and I would like to ask you for your opinion on an approach we have been employing:
You essentially officially recommend against Bloc-to-Bloc communication, and I would consider the main reason controversial:
Where does this rule come from? If you take a look at e.g. onion or port-adapters (hexagonal) architectures, nowhere do they prohibit components in the same layer ('siblings') from talking to each other. The only 'bad'/wrong dependencies are pointing 'outside' (e.g. a repository knowing a Bloc). I have a lot of backend experience and it is also day-to-day business to call a component with business logic by another component in the same layer.
provider
orriverpod
(to name the most written-about ones recently, apart from Bloc), they actively promote it (withProxyProvider
andRef.watch(someOtherProvider)
, accordingly).build
method. For example, when the UI depends on the state ofBlocB
, which in turn is triggered by a change inBlocA
, you need aBlocListener<BlocA>
and aBlocBuilder<BlocB>
, where the listener is nested within the builder. (And this is just one use case, there are others.) For these reasons our team decided to completely reject this approach.My example use case is the following (silly and contrived): there is a
WeatherBloc
that gives me weather forecast (it changes as the day goes by, and the Bloc periodically calls the repository to get weather data) andActivityBloc
which suggests what I could do now.ActivityBloc
gets favourite activities for the current user from a repository, but what it suggests takes weather forecast into account - it will not suggest taking a walk if the forecast is a downpour. As you can see,ActivityBloc
could really use some help fromWeatherBloc
.(BTW, I keep saying Bloc this, Bloc that, but in reality I always start off with a
Cubit
; all that I write here still applies.)WeatherBloc
, I want to split all of this.WeatherRepository
in both Blocs, butActivityBloc
would pretty much redo a lot of logic thatWeatherBloc
already does - the polling in this case.WeatherRepository
and implements the polling, and both Blocs could use it. But this would introduce yet another layer which is not really talked about in any Flutter-related documents. It would also work only on one level of dependencies -ActivityBloc
needs forecast information - but what if some other components need to work with activity suggestions as their input? This could get pretty complex.Now the solution that I have been very successful with in a few projects - Bloc-to-Bloc communication using abstractions:
WeatherBloc
get its weather data from aWeatherRepository
and issuesWeatherState
s - pretty normal stuff.ActivitiyBloc
gets the list the user's favorite activities fromActivityRepository
, AND ALSOWeatherState
fromWeatherBloc
, intersect these two pieces of information and provideActivitySuggestions
based on that.ActivityBloc
doesn't useWeatherBloc
directly, nor vice-versa.ActivityBloc
gets aStream<WeatherState>
and when the stream emits a new event, it triggers emitting new activity suggestions.Bloc
just so happens to be able to give me aStream
, which is really neat.(I think Bloc used to immediately give the current state on subscription way back in the past, then it changed, that's why I 'prefix' the stream with the current state. In my app I have an extension for it.)
This approach been working wonderfully for me:
ActivitySuggestions
triggering a change in some other Blocs/features.)ActivityBloc
knows aboutWeatherState
- but is this bad? It does need the forecast data in some form, and it makes it possible without introducing extra code for the sake of abstractions, or more layers.StreamController
and trigger changes and test reactions.I have never had to wire the Blocs the other way round: continuing from my example above, I never needed
WeatherBloc
to knowActivityBloc
to trigger a change. In my head this would be backwards (asWeatherBloc
would need to know all of the Blocs that depend on it). I could make it work by just giving an abstractSink<WeatherState>
toWeatherBloc
, and theSink
would be a 'multicast sink' that would in turn push the new state to other Blocs, and all that would be wired when the application is constructed. But I have never needed it this way, it does sound much more complex, and wrong.I would be interested in your thoughts about our approach described in this essay. Is it a violation of some principles that don't occur to me?
Cheers.