What's the best approach to make something like parent-children bloc architecture? Very often I face a problem when there is a need to establish communication between cubits or blocs.
Let's image a form where we would like to create a new user:
User name
User role (separate cubit, fetch all possible option from API)
Calendar (separate cubit, a lot of calculations made)
Apply button (main BLoC which is responsible to save new user). First
approach:
We need to get each cubit's state and emit new state when clicking on Apply button.
First approach:
var role = context.read<RolesCubit>().state as RoleSelected;
var date = context.read<CalendarCubit>().state as DateSelected;
context.read<CreateUserBloc>.add(CreateNewUserEvent((role: role, date: date));
Pros:
good granulation,
each small cubit is responsible for one thing eg getting a list of available roles and choose one
easier to maintain tests,
cubits can be reused in many places
Cons:
a need to remember to update in each place where main CreateNewUserEvent is added (imagine we would like to add 3rd sub cubit responsible for Access or Salary).
need to know each sub-cubit's state before we add "main" event. I should copy this logic to other places where this event is added. Maybe another example: I wanted to follow this approach with paginated list, on scroll refresh indicator action and one of sub page, therefore I had to check each sub-cubit state and after that I could add main event CreateNewUserEvent;
Second approach:
context.read<CreateUserBloc>.add(DateChanged((newDate));
context.read<CreateUserBloc>.add(RoleChanged((newRole));
context.read<CreateUserBloc>.add(CreateNewUserEvent(());
Create one big BLoC which can do all responsibilities.
Pros:
everything is kept in one place. When we select new role or new date,
"master" BLoC is immediately informed about change and decides what
to do next.
Cons:
big piece of code which is hard do maintain and test smaller parts
can not be reused in other places because it's monolith
Third approach
Stream subscription and make "master" bloc strictly dependent of sub-cubits. Master's cubit constructor requires a stream of sub-cubits. Explained here:
I hope I explained a problem in a simple way. Whats the best approach to maintain complex architectural design in flutter?
Related
What are the actual advantage(s) of Bloc over Cubit?
In addition to traceability (which you can also achieve with appropriate logging in Cubit), and advanced event transformations (I can't think of any "advanced" event transformations that Cubit can't do, since there is always a way to do it with Cubit. And if you're using clean architecture, domain/data layer can help with complex data manipulations).
Sharing and sourcing events
These are the things that I'm looking for that should be able to do with Bloc since these things can't be actually done with Cubit. However, it appears that these are impossible (or is it?) because adding event on a Bloc requires you to identify the actual Bloc where the event will be added. bloc.add(YourEvent()).
Also, event sharing is somewhat debatable because this can lead to a bad architecture/hard to maintain.
For event sourcing, I can't find in the docs if this is possible (reversing back to a specific past state?).
Am I missing something here?
As far as I know reversing to past state can be easily done when you have immutable states regardless of whether it is bloc or cubit. Having immutable states allows you to store list of states and restore whenever you need a specific state.
Bloc has no advantages over cubit but rather different purpose. In cubit you have action=>response (function=>states) whereas in bloc you have streams.
What cubit cannot do?
For example you can have two events being processed concurrently when using bloc (since bloc 7.20) but you cannot call two functions simultaneously on cubit.
Sharing events
You can share events implementation between different blocs because you have to specify what events bloc implements.
class MyBlocA extends Bloc<MyEvents, StatesA>
class MyBlocB extends Bloc<MyEvents, StatesB>
If I understood correctly, what you want to do is to process a single event in two different blocs, which you cannot do because event is emitted to a specific bloc. So it requires two calls:
blocA.add(EventA);
blocB.add(EventA);
Depending on your case you might listen to state of MyBlocA inside MyBlocB. This way whenever event for MyblocB appears, the action would depend on the state of MyBlocA.
Blocs and Cubits are same in terms of state management with only one difference of state mutation: Bloc is event driven and Cubit is method driven.
Apart from all of this, in terms of architecture not much is different as of now. All others have been mentioned precisely by #chris in the above. It is up to the developer on how to maintain the state in a way is manageable for us.
aren't events for dependency injection?
Cubit
call Action
set State
Bloc
trigger Event (inject something, depending on screen/page it was trigger from. as Example)
then call Action
and set State
My app requires to consume different attributes of a User object in different sections of the app i.e. different screens and dialogs.
Example of User attributes would be a list of followers and following.
I need this User object to be reactive, i.e. should append a new user (response from api call) to it's following list when the subsequent action is performed.
Currently I plan to use certain BLoC's as Reactive View Models (from MVVM) for each screen (so that the business logic is separated from the presentation layer) and have a global User BLoC listening to these View Models.
For example :- User BLoC can listen to local View Model BLoC for follow user page and when the view model emits a successful state the response can be added to the User BLoC's User state's following list.
How feasible and performant would an implementation like this be? Is this the standard way of doing things in BLoC. I looked around for examples like these but could only find simple implementations of BLoC architecture. Would this be too over kill for a mid sized app?
Much appreciated!
I have multiple model objects delivered by some data source in my Flutter app, each is identified by its ID (an integer field).
Each model and its state can be represented in multiple widget.
I'm making a bloc object for each model object (for example a model with ID of 3 shown in multiple widgets and they should share the same bloc instance).
From some of these widgets, I can send an event to the bloc instance, and have a new state, and of course, this state should be represented in all of the widgets that are sharing the bloc instance.
The scenario in my mind should be as follows when fetching the data:
1- Each model object fetched from some data source and is delivered to the presentation should search for its corresponding bloc instance.
2- If the bloc instance is created, use this bloc instance (and update its state if necessary).
3- If the bloc instance is not created, create one and use it.
My main problem is, I can't seem to be able to create those bloc instances globally, and access them in a "dependency injected" way. The dependency is the bloc instance and identified by the model ID.
I normally use get_it to register singleton instances, but now I need "Singleton instances uniquely identified by the model IDs"
Also, since blocs opens streams, these should be closed by hand since I think we can't use BlocProvider widgets in my case. If there is a solution to that, that would be much appreciated too.
I have a screen (a stateless widget) in which I have two buttons, one to check if Firebase account is verified and the second for resending verification email. These two processes are obviously separate and I need to separate them in two cubits.
So, my question is how can I do it?
I guess I can do that with MultiBlocProvider, but I am not sure how can I get states from those two cubits in this scenario? Is there a best practice defined for this type of scenario?
First, it is no problem to run a MultiBlocProvider to the screen and then have two BlocBuilders in parallel, e.g. one for each button or nest two BlocBuilders (which is essentially what .watch() would be if it is in another BlocBuilder).
But I have an opinionated answer as well. The two buttons will as you say run two separate processes, but I wouldn't separate them into two cubits as they are so tightly linked to the same feature and essentially the same state of that feature. I think a clean solution will be one cubit with separate methods, a HandleFirebaseVerificationCubit with a resendEmail()-method and a checkVerification()-method. Because I guess you'd want to either hide or disable buttons depending on a common state (e.g. NotVerified/Verified) etc.
I'd compare this to a form where you have a backend handling e.g. some validation which is triggered by a buttonpress and another button fetching some data to assist with populating textfields (e.g some address) You'd probably have one cubit for the entire form.
The Bloc manual describes the example of a simple Todos app. It works as an example, but I get stuck when trying to make it into a more realistic app. Clearly, a more realistic Todos app needs to keep working when the user temporarily loses network connection, and also needs to occasionally check the server for updates that the user might have added from another device.
So as a basic data model I have:
dataFromServer, which is refreshed every five minutes, and
localData, that describes what changes have been made locally but haven't been synchronized to the server yet.
My current idea is to have three kinds of events:
on<GetTodosFromServer>() which runs every few minutes to check the server for updates and only changes the dataFromServer,
on<TodoAdded>() (and its friends TodoDeleted, TodoChecked, and so on) which get triggered when the user changes the data, and only change the localData, and
on<SyncTodoToServer>() which runs whenever the user changes the todo list, or when network connectivity is restored, and tries to send the changes to the server, retrieves the new value from the server, and then sets the new dataFromServer and localData.
So obviously there's a lot of interaction between these three methods. When a new todo is added after the synchronization to the server starts, but before synchronization is finished, it needs to stay in the local changes object. When GetTodosFromServer and SyncTodoToServer both return server data, they need to find out who has the latest data and keep that. And so on.
Coming from a Redux background, I'm used to having two reducers (one for local data, one for server data) that would only respond to simple actions. E.g. an action { "type": "TodoSuccessfullySyncedToServer", uploadedData: [...], serverResponse: [...] } would be straightforward to parse for both the localData and the dataFromServer reducer. The reducer doesn't contain any of the business logic, it receives actions one by one and all you need to think about inside the reducer is the state before the action, the action itself, and the state after the action. Anything you rely on to handle the action will be in the action itself, not in the context. So different pieces of code that generate those actions can just fire these actions without thinking, knowing that the reducer will handle them correctly.
Bloc on the other hand seems to mix business logic and updating the state. API calls are made within the event handlers, which will emit a value possibly many seconds later. So every time you return from an asynchronous call in an event handler, you need to think about how the state might have changed while that call was happening and the consequences this has on what you're currently doing. Also, an object in the state can be updated by different events that need to coordinate among themselves how to avoid conflicts while doing so.
Is there a best practice on how to avoid the complexity that brings? Is it best practice to split large events into "StartSyncToServer" and "SuccessfullySyncedToServer" events where the second behaves a lot like a Redux reducer? I don't see any of that in the examples, so is there another way this complexity is typically avoided in Bloc? Or is Bloc entirely unopinionated on these things?
I'm not looking for personal opinions here, only if there's something I missed in the Bloc manual (or other authoritative source) about how this was intended to work.