Keeping track of multiple states with Flutter BLoC pattern - flutter

I'm trying to make a authentication screen with the BLoC pattern, but I have doubts about best practices even after reading the documentation of the flutter_bloc lib and reading multiple implementations.
The main problem I have is about state management. My authentication screen should handle the following states:
Password visible or not.
Show register or login screen.
TextEdit validation.
Loading (waiting for a Firebase response).
if authenticated, show home. Else show the authentication page
I really wanted to do all the logic in blocs to make a clean architecture.
Some people say they use a bloc per screen, sometime a bloc for the whole app, but flutter_bloc library says you should use a bloc for any widget complex enough.
My problem is, how can I deal with multiple states?
If I store variables in states classes extended from the mother AuthState class, I can't access it in mapEventToState
because the block receives the mother just the AuthState class.
I tried handling all states in the same bloc by passing the current state in a event (as I will show in the code below), but then I can't properly set the initial state.
What's the best practice solution?
Passing all state classes as variables in the mother AuthState class?
Then I could persist data using "state.passwordFieldState". But I
never saw something like that. I bet it's a wrong approach.
Create a model to store the state and manipulate the model when a change event enter the bloc?
Creating multiple blocs or cubits? One cubit for authentication, 1 cubit for password visibility, one bloc for authentication handling? My concern with that would be nesting bloc builders.
Or should I forget about blocs for simple things and use providers instead? I wouldn't like to do that because it can lead to spaghetti code.
Here's my code:
class AuthState extends Equatable{
#override
List<Object> get props => [];
}
class PasswordFieldState extends AuthState{
final bool isObscured;
PasswordFieldState({this.isObscured});
#override
List<Object> get props => [isObscured];
}
class AuthEvent extends Equatable{
#override
List<Object> get props => [];
}
class SetObscurePassword extends AuthEvent{
bool isObscured = false;
}
class passwordTextEditChanged extends AuthEvent{}
class emailTextEditChanged extends AuthEvent{}
class AuthBloc extends Bloc<AuthEvent,AuthState> {
AuthBloc(AuthState initialState) : super(initialState);
#override
Stream<AuthState> mapEventToState(AuthEvent event) async* {
if (event is SetObscurePassword) {
yield PasswordFieldState(isObscured: !event.isObscured);
} else if (event is passwordTextEditChanged) {
print("validation handling");
} else if (event is emailTextEditChanged) {
print("validation handling");
}
}

I think I found the answer.
It's not possible to make a bloc for the entire application using the flutter_bloc library. It's only possible to do such thing using stream controllers to create a bloc without the library.
But it's not a bad thing to create a bloc for each different task. It makes the code more testable and easier to understand.

Related

Flutter BLoC variables best practice

Started recently using the BLoC approach for building apps, and one thing that is not clear is where to "keep" BLoC variables. I guess we can have these two options:
Declare a variable in the BLoC class; for example in my class I can do the following:
class ModulesBloc extends Bloc<ModulesEvent, ModulesState> {
late String myString;
}
And access it in my UI as follows:
BlocProvider.of<ModulesBloc>(context).myString;
Keep it as a state variable; for example I can declare my state class as follows:
class ModulesState extends Equatable {
const ModulesState({required this.myString});
final String myString;
#override
List<Object> get props => [myString];
}
And access it in my UI as follows:
BlocBuilder<ModulesBloc, ModulesState>(
builder: (BuildContext context, ModulesState modulesState) {
modulesState.myString;
}
)
Are there any performance penalties / state stability issues with any of the above approaches?
Thanks!
I am not sure there is an absolute answer but I can at least give my opinion.
In bloc you have 3 objects: bloc, event, state.
The state is the mutable part while the bloc is a description of the your problem (what states to emit for each event). As such, an immutable variable to describe your problem should be, in my opinion, placed inside the bloc. However, anything which might change is the state of your bloc (same as the state of your widget) and should as such be stored in the state.
Example:
You want to create an app where you can set timers. In this app you can have multiple timers, each of which will be identified by a name.
In this case:
your state will be an object containing a double variable called timeCount, which will be incremented each seconds for example.
You bloc will have a final field called name which will have to be set during the creation of the stopwatch.
Interestingly enough, if you want the bloc to also handle the stopwatch creation, you will have 2 states: the first empty, the second with a name and timeCount. See how naturally name became variable and is therefore found in the state now.

Getting a stream of events in a Bloc

I have two blocs - BlocA and BlocB. The latter adds events into the former.
class BlocA extends Bloc<BlocAEvent, BlocAState>{....}
class BlocB extends Bloc<BlocBEvent, BlocBState>{
final BlocA blocA;
....
Stream<BlocBState> mapEventToState(BlocBEvent event) {
if(event is NotifyBlocA) blocA.add(FromBToAEvent());
}
}
Everything is working fine. However, I need to write down a test that makes sure that when BlocB receives a NotifyBlocA event, BlocA does indeed receives a FromBToAEvent event. Is there a way to get the events stream of BlocA to check that? In the documentation, _eventStream is private. Is there a workaroud for this if no means are available at the moment?
NOTE: One could argue to look for BlocA state changes (ie BlocA.stream) and check whether correct states are emitted in response to FromBToAEvent. However, I need to write a unit test for BlocB without depending on the correctness of BlocA.

Why does ChangeNotifierProvider exist?

According to Flutter's documentation here (https://flutter.dev/docs/development/data-and-backend/state-mgmt/simple),
one way to control the state management is to use ChangeNotifierProvider(or InheritedWidget) when one of its descendants is a Consumer which is rebuilt when the underlying ChangeNotifier changes.
Flutter's team reiterates that approach on the official youtube Flutter channel.
However, that means that some business logic element (ChangeNotifier) is now part of the Widget tree (which otherwise is all about how the UI will look like). So I don't understand why those classes (ChangeNotifierProvider,InheritedWidget,Consumer) even exist.
In particular, why isn't the following approach superior:
-Business logic is a singleton and a ChangeNotifier.
-Instead of Provider(...), for every Widget depending on the business logic simply do this:
BusinessLogicSingleton.instance.addListener(() {
setState(() {
});
at initState(...).
What am I missing here?
You can indeed do that, but then you'd also have to remember to close the listener on dispose. Providers handle that automatically.
How do you plan to set state in stateless widgets? Also following your suggested method, you would be rebuilding the entire widget with setstate, vs building only a specific part even for complex widgets if you were to use consumers.
I think there are benefits to have the ChangeNotifier a part of the widget tree. You need to ask yourself: what are the features of the Widget Tree that can benefit ChangeNotifier? Or rather - by using Widget Tree, can I get everything I get from Singleton? And can I do some things Singleton does not provide?
First - if you put your ChangeNotifier high in the tree, and you create only one ChangeNotifier for your class (e.g. ChangeNotifier) - you practically have a singleton. With added benefit of a Widget Tree logic taking care of disposing your ChangeNotifier etc.
Second - you can have multiple ChangeNotifiers of the same class in different parts of the tree, practically scoping your ChangeNotifier any way you want. This is not something a Singleton approach can offer. I'm not sure this is a good example - but imagine your ShoppingCart demo: it has a single ShoppingCart ChangeNotifier provider at the top of the tree.
Now imagine one of the items you sell are customizable - like Pizza. And when user selects Pizza - you open additional screen where a user will chose toppings. Now, this additional screen could have a new instance of ShoppingCart - tracking the toppings you added, with all the features of the shopping cart - add, remove etc. This way your module (Pizza toppings selection) becomes self-contained - it does not expect a Singleton to be provided for it, but it will inject it's own ChangeNotifier into the tree.
With the Singleton approach you would need to come up with another way - extending your shopping cart for the sake of creating another Singleton. Not very practical.
Following up on the question, the following hyper-simple class saves me a lot of boilerplate code, and illogical code (forcing ChangeNotifier to be a part of the Widget tree via Provider etc)
import 'package:flutter/material.dart';
//A class which makes it easier to worker with change notifier without provider/consumer which looks wrong since changenotifier shouldn't be forced into widgets tree for no reason
//eg widgets should represent UI and not business logic
class ListenerWidget extends StatefulWidget {
const ListenerWidget({Key? key,required ChangeNotifier notifier,required Widget Function(BuildContext) builder,void Function()? action}) : notifier=notifier,builder=builder,action=action,super(key: key);
final Widget Function(BuildContext) builder;
final ChangeNotifier notifier;
final void Function()? action;
#override
_ListenerWidgetState createState() => _ListenerWidgetState();
}
class _ListenerWidgetState extends State<ListenerWidget> {
void emptyListener(){
setState(() {
});
}
#override
void initState() {
widget.notifier.addListener(widget.action??emptyListener);
super.initState();
}
#override
void dispose() {
widget.notifier.removeListener(widget.action??emptyListener);
super.dispose();
}
#override
Widget build(BuildContext context) {
return widget.builder(context);
}
}

Is it allowed to add event to another bloc from inside of a bloc?

I am using bloc library available in Dart to implement "bloc" pattern. I will eventually move onto flutter_bloc library so I can use it inside a real app.
I'm having a bit of difficulty understanding how to create some general blocs that can be called from within more specialized blocs. By specialized I mean some bloc that perhaps manages a specific view. General blocs would then take care of calling APIs or maybe even doing multiple things sequentially.
So my idea is that perhaps I have a StateA that manages certain model and for that reason I use BlocA. Whenever certain event is added to BlocA, I also need to update StateB that is managed by BlocB. I do not want to do this within same bloc because those different states contain different data that might be unrelated. Perhaps I can then have BlocC that is used for specific part of application but certain event should also invoke events and state changes in BlocA and BlocB.
I am considering writing BlocA bloc like this:
class BlocA extends BlocBase<BlocAEvent, BlocAState> {
BlocA(BlocB blocB) : super(BlocAState()) {
_blocB = blocB;
_blocARepository = BlocARepository();
};
BlocARepository _blocARepository;
#override
BlocAState mapEventToState(BlocAEvent event) async* {
if (event is BlocAEventOne) {
yield state.copyWith(valueOne: event.value);
} else if (event is BlocAEventTwo {
// Get data related to BlocAState
final data = await _blocARepository.fetchImportantData()
// ! <-- I also need to fetch additional data but that is managed
// by different bloc - BlocB
_blocB.add(BlocBEventOne(id: data.id));
yield state.copyWith(dataList: SomeModel.fromJSON(data));
}
}
}
and then just creating regular bloc BlocB like this:
class BlocB extends BlocBase<BlocBEvent, BlocBState> {
BlocB() : super(BlocBState()) {
_blocBRepository = BlocBRepository();
};
BlocBRepository _blocBRepository;
#override
BlocBState mapEventToState(BlocBEvent event) async* {
if (event is BlocBEventOne) {
// Get data related to BlocBState
final data = await _blocBRepository.fetchOtherData(event.id)
yield state.copyWith(dataList: SomeOtherModel.fromJSON(data));
}
}
}
I am really unsure if this is correct approach as basically BlocA adds another event. But I am trying to do some reusable blocs and keep data more separate. I know I could also do _blocB.stream.listen in BlocA but that only gives me the state of the BlocB bloc. What I need is the ability to react to certain events.
Do you think that my approach is perhaps convoluted and using BlocObserver would perhaps be more appropriate?
The problem with BlocObserver is that I am unsure how to use it properly from within Flutter app.
First of all, what you are trying to achieve is completely fine, but let's talk about the architecture a little bit.
One option could be that BLoC B subscribes to BLoC A state changes and handles that accordingly. For instance, here a more specific BLoC subscribes to changes of a more generic one: https://github.com/felangel/bloc/blob/08200a6a03e37ce179cef10b65f34ddf6f43f936/examples/flutter_todos/lib/blocs/filtered_todos/filtered_todos_bloc.dart. For that, you would need to adjust the BLoC A state a bit (so you could identify this specific change/event) which could be not a way to go.
Another thing you should consider is whether you want to tightly couple your BLoCs (BLoC B reference is passed to BLoC A via constructor in your example). For that, you can create some kind of mediator class that uses streams inside: BLoC A could publish an event to that mediator class sink and all the subscribers will receive it via exposed streams (in your case - BLoC B should subscribe to that stream) - so the communication there would be like BLoC A -> Mediator -> BLoC B. That's basically what Mediator/Observer OOP design pattern resolves. The advantage of it is that your BLoCs won't know anything about each other, multiple BLoCs could subscribe to multiple different event streams, meaning you could add more subscribers (BLoCs) to your events at any point.
You can define a callback in your event and invoke it in your bloc like this:
blocA.add(BlocAEvent(whenDone: () {
blocB.add(BlocBEvent())
}));
Another way to do this is to listen to events being published by a BloC:
// Inside some widget
this.context.read<MyBloc>().listen((myBlocState) {
if (mounted && myBlocState.isSomeState) {
this.context.read<MyOtherBloc>().add(MyOtherBlocEvent());
}
});
This solution would ensure your BloCs are not coupled and you can chain the publishing of your events based on the state of another BloC.

Flutter Bloc - How to correctly use parameters in bloc

I'm new with the flutter bloc architecture.
I have an AuthBloc that emit a Stream of User.
Now, in some my lower level blocs, I need to access the id of the User, such as in a UserProfile Bloc.
I cannot decide which is the best approach for that.
For Instance I could:
Subscribe to the user Stream of the bloc and fire a newUserEvent every time the user change. This could work well, but since I then have to subscribe to a stream of user profile edits from the repository the logic seems clunky.
The second approach I thought of was of instantiating the bloc by passing the UserId as a parameter when I create it in the blocProvider of that route, for exemple passing it as a route parameter.
class UserBloc extends Bloc<UserEvent, UserState> {
final String userId;
final UserRepository _userRepository;
StreamSubscription? _userDetailsSubscription;
UserBloc(this.userId, this._userRepository) : super(UserState.loading()) {
_userRepository.getUserDetails(userId).listen((userDetails) {
add(UserDetailsChanged(userDetails));
})
..onError(
Finally I could also assume the user is already authenticated when instanciating UserBloc and I could just retrieve from the userRepository the last value of the authRepository user stream. I like probably this approach best, but the drawback is that my streamSubscription of userDetails become a Future :
class UserBloc extends Bloc<UserEvent, UserState> {
final UserRepository _userRepository;
StreamSubscription? _userDetailsSubscription;
UserBloc(this._userRepository) : super(UserState.loading()) {
_userRepository.getUserDetails().then((stream) {
_userDetailsSubscription = stream.listen((userDetails) {
add(UserDetailsChanged(userDetails));
})
..onError(
I tried to find the reccomended way to approach this but I couldn't find any firm opinion.
The second option sounds the simplest, but the first one sounds like the correct one.
What you described in the first option is just simple BLoC-to-BLoC communication and I do not see the "clunkiness" in this solution. You can find some examples in bloc library Github repository, e.g.: https://github.com/felangel/bloc/blob/master/examples/flutter_todos/lib/blocs/filtered_todos/filtered_todos_bloc.dart.
I do not think there is an "official" solution, you just need to choose the one that fits to your problem.