From the docs I understood that one can call addListener() on a ChangeNotifier instance to add a custom listener to the stack.
This method accepts a callback with zero arguments (according to notifyListeners()), e.g.:
class MyClass extends ChangeNotifier {
MyClass() {
addListener(() {
// ...
});
}
}
From within the callback, how does one find out what properties or parts of MyClass have been changed?
ChangeNotifier does not have such capabilities inherently. You will have to implement your own logic. Specifically, you either have access to all of the properties of your ChangeNotifier implementation because you add the listener in its scope or you have access to it because you have a reference to it in your scope.
ChangeNotifier simply implements Listenable and provides some utilities for managing listeners. Furthermore, the documentation states the following about it:
ChangeNotifier is optimized for small numbers (one or two) of listeners. It is O(N) for adding and removing listeners and O(N²) for dispatching notifications (where N is the number of listeners).
I am not sure about options with better runtime complexity for notifying listeners, but you will not run into any issues in a regular Flutter app.
ValueNotifier
ValueNotifier is a pre-made implementation of ChangeNotifier that will notify its listeners when its value property is changed.
This is sufficient for most case, but since it appears that you want to create a custom ChangeNotifier, you can use the source code of ValueNotifier to take a look at an example implementation (it is very straight forward).
If you are just looking to do state management in general, ValueNotifiers usually work great. However, they are not applicable in every scenario. Hence, here is an extensive list with different state management options.
Considering the questions, I think the techniques that fit your needs best and the most popular options are the following:
InheritedWidget as it lets you notify dependents based on what data changed. Additionally, there is InheritedModel as an extension of this and InheritedNotifier that works with Listenable, just like ChangeNotifier does.
The BLOC pattern, which works with streams.
The provider package which is mostly a convenience wrapper for various Flutter state management techniques (InheritedWidget, StatefulWidget, ValueNotifier, etc.).
Related
Looking through the class that defines BlocObserver, all the methods do not have any functionality defined. The BlocObserver does not inherit anything from any other class. It is only connected to the Bloc class by being created during instantiation of a Bloc.
How do the methods in BlocObserver have functionality when they are empty inside BlocObserver?
Read through the BlocObserver definition, and read through the Bloc definition.
What to do
The way you are expected to use BlocObserver is described pretty well in Core Concepts.
Basically, as BlocObserver is an abstract class, you would extend it in your own class, providing implementations for the handler methods as appropriate for your use-case.
So, roughly:
class CustomObserver extends BlocObserver {
#override
void onChange(BlocBase bloc, Change change) {
super.onChange(bloc, change);
// Perform logic based on the change
}
}
Then, you would assign an instance of this class as the static observer on Bloc, for example:
Bloc.observer = CustomObserver();
After this point, you would expect any changes that propagate through Bloc to call your CustomObserver.onChange() method.
How this works
The pattern of providing some framework object a definition of the code you'd like to run when certain events happen is a pretty common one, so it's useful to come to grips with it. Usually (and also in this case) it's way simpler than it appears.
As discussed above, you provide a BlocObserver to Bloc by setting a static member. This means both that:
you can only have one observer in the system at a time, and
any code can access it directly by calling Bloc.observer
Then, when making state changes, you ensure you do so via an instance of BlocBase (such as Cubit), which takes care of calling the appropriate method on our observer.
So, once again using Core Concepts as a base, when calling CounterCubit().increment(), the call stack looks like this:
CounterCubit.increment
CounterCubit.emit/Cubit.emit/BlocBase.emit (through inheritance)
CounterCubit.onChange
BlocBase.onChange
SimpleBlocObserver.onChange
At this point, you're back in your own code, and you can see that SimpleBlocObserver.onChange(...) calls super.onChange(...). No magic, just function calls.
Building off class that indirectly extends from StateNotifier in StateNotifierProvider doesn't work
I will have abstract DogNotifier extends StateNotifier<DogInfo> and some children notifiers ShibaNotifier, that extend it.
However, in the UI, not sure the best way to handle:
I was thinking of having another provider aka KennelNotifier which would keep track of which Dog we are looking at in a DogNotifier whichDog() method.
So that I can reuse the UI like:
ref.watch(ref.read(kennelNotifier).whichDog))
But this has to be wrong or is it ok? Wondering how I can reuse my UI code for a Dog in a conditional way like this as I still would like a ShibaNotifier and a LabradorNotifier etc.
Thank you.
So,
it probably is possible to get providers dynamically: https://github.com/hunterpp/riverpod_child_provider/blob/ff30883e5c502b5e821f3c4bb8cc4a93dec21d65/lib/main.dart#L53
but it involved multiple calls to ref.watch:
Text(ref.watch(ref.watch(kennelProvider).dog.notifier).produce()),
And not sure that is idiomatic and feels risky.
What I settled on was using family:
https://github.com/hunterpp/riverpod_child_provider/blob/main/lib/main.dart
And explicitly passing a type to get the correct child notifier
Text(ref.watch(dogProvider(DogType.pittie).notifier).produce()),
Text(ref.watch(dogProvider(DogType.shiba).notifier).produce()),
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.
I have a class Communicator which where I've declared a few functions. Executing those function takes some time and an update of progress is required till all the functions get executed.
Now, I am using the functions of class Communicator in my stageful widget class HomeScreen. I want to pass the progress data from Communicator to HomeScreen and update the widget regularly upon receiving the progress data. How can this be achieved?
Communicator.addSomeListener(_handleSomeEvent); in init, and Communicator.removeSomeListener(_handleSomeEvent) in dispose.
void _handleSomeEvent(SomePayload data) => setState((){});
Your singleton can keep a list of these callbacks, and call them.
One way to that using callbacks like shawnblais explained. You can also do it by state management if you like. Provider will be a good place to start.
I am a bit confused about the new release of Bloc: 6.0.0, adding Cubit notion, is the bloc depreciated or we can use both of them?
Cubit is a subset of the BLoC Pattern package that does not rely on events and instead uses methods to emit new states.
So, we can use Cubit for simple states, and as needed we can use the Bloc.
UPDATE : additional comparison
There are many advantages of choosing Cubit over Bloc. The two main benefits are:
Cubit is a subset of Bloc; so, it reduces complexity. Cubit eliminates the event classes.
Cubit uses emit rather than yield to emit state. Since emit works synchronously, you can ensure that the state is updated in the next line.
Cubit is perfectly suitable to any app scale and anyone saying one scales better than the other is ill informed.
You have to chose between traceability with an event driven architecture which comes with boiler plate vs more traditional code. One advantage of event driven architectures is that events can be used by 0 or many handlers, the emitter does not care. The disadvantage is the indirection and extra boiler plate.
Traceability
When you use a bloc you have a Transition that contains the event:
Transition {
currentState: AuthenticationState.authenticated,
event: LogoutRequested,
nextState: AuthenticationState.unauthenticated
}
When you use cubit it does not
Transition {
currentState: AuthenticationState.authenticated,
nextState: AuthenticationState.unauthenticated
}
That alone gives you traceability because you know which event triggered the changes just by looking at the logs. You see eventA happened then eventB. In practice though, for the cubit you can often infer traceability from the changes themselves without the "label", because there is not many action that can output this change. That traceability gives the traditional advantage and disadvantage of event driven architecture. One disadvantage is indirection cost which shouldn't be understated, you cannot put a debugger and follow the code here by definition, another would be the extra boiler plate.
Event sharing
In essence, emitting an Action/Event that is then mapped to call a function, is just like calling the function directly. The only fundamental change is when an action must be consumed by multiple consumers, blocs or others (example analytics). If an action must be shared between blocs (example reset data on logout), then blocs can be handy in that area. However it might be a better architecture model not to mix one bloc with another, or it might not be, it depends. It's a judgment call here.
Conclusion
If you ask yourself should I use bloc or cubit, imo you should go with cubit. Once you have a need for Bloc you can switch, but doing so before even having the need is imo preemptive architecture.
I don't agree with opinions that you can only use Cubit only for simple cases, or for small apps. I would even say that Cubit can handle pretty complex cases without the need to be converted into a Bloc.
The main advantage of Cubit is that it has less boilerplate code, and has a straightforward and synchronous way for emitting states (as there is no Event class, but a simple functions instead). In most cases you won't need a Bloc, so you can easily use Cubits if you don't have to transform your events. In those rare cases when you need to transform events, you can easily refactor Cubit into a Bloc, as both of them extends BlocBase class.
The main difference between Cubit and Bloc is that in Bloc you have Event class in addition to State. So, you are able to use EventTransformer function in order to manipulate your events. For example, you can add debounce or throttle to your event. Or even have some complicated event stream mapping. That's the main benefit of using a Bloc instead of a Cubit.
An example of using EventTransformer for debouncing event:
import 'package:stream_transform/stream_transform.dart';
EventTransformer<Event> _debounce<Event>(Duration duration) {
return (events, mapper) => events.debounce(duration).switchMap(mapper);
}
Usage of the _debounce in event mapping:
class ExampleBloc extends Bloc<ExampleEvent, ExampleState> {
const ExampleBloc()
: super(const ExampleState()) {
on<ExampleEvent>(
_onExampleEvent,
transformer: _debounce(Duration(seconds: 1)),
);
}
...
}
Basically, that's the core difference between Cubit and Bloc.
P.S. Usage of Blocs / Cubits in projects is also quite opinionated, and some teams might use Blocs only due to code consistency.
The Cubit is suitable for simple State management where you just have one kind of event to bind to the state. While Bloc is for complex state management where you can have many events to map to states.
For example let consider below Cubit
class SimpleCubit extends Cubit<SimpleState> {
SimpleCubit () : super(InitialState());
void updateState(String stateName){
emit(NewState(stateName));
}
}
Let now have a look on Bloc
class SimpleBloc extends Bloc<SimpleEvent, SimpleState> {
SimpleBloc() : super(InitialState()){
on<SimpleEven1>(_handleEvent1);
on<SimpleEven2>(_handleEvent2)
}
Future<void> _handleEvent1(SimpleEvent event, Emitter<SimpleState1> emit) async {
// Put your code here
emit(SimpleState1(event.args))
}
Future<void> _handleEvent2(SimpleEvent event, Emitter<SimpleState2> emit) async {
// Put your code here
emit(SimpleState2(event.args))
}
}
To implement above code with Cubit we will need switch cases (boilerplate)
Bloc force you to map each event to a separate function which is good when you have a complex state.