I have a very big, complex app with lots of pages with input forms with lots of fields (texts, date-selection, combo-boxes,...), so the state is rather big.
I'm switching from Provider to Riverpod, and the proposed way for state management seems to be primarily StateNotifierProvider. But that state is immutable, meaning you have to clone the complete state on every change.
What I would need is Providers ChangeNotifierProvider (that Riverpod has too), in order to be able to have mutable state and more control over when rebuilds on the listeners are triggered.
But Riverpod discourages the use of ChangeNotifierProvider, what makes me a little nervous. Maybe I didn't really understand the StateNotifierProvider concept.
So the question is, how can I use StateNotifierProvider to handle complex state? Is it really necessary to always clone the state?
Edit: With complex I mean a larger number of fields (e.g. 50) and also lists and object-structures. You could clone that, but it's expensive and it doesn't feel natural/smart to do this on every little change. Is this really the intended way? Is there a way around cloning while still using StateNotifierProvider?
First of all i wouldn't recommend using ChangeNotifier(mutable state) over StateNotifier(immutable state), as best practical way of state management in flutter is to use immutable state.
You can use freezed and freezed_annotation to help you copy the state each time you want to emit new one, you just copy the old state and change what you need.
Something like this
state = state.copyWith(var1: "new var");
here is an example of complex state using freezed
add freezed_annotation to your dependencies
add freezed and build_runner to your dev_dependencies
create your state complex_state.dart something like this, change the factory constructor to match your needs
import 'package:freezed_annotation/freezed_annotation.dart';
part 'complex_state.freezed.dart';
#freezed
class ComplexState with _$ComplexState {
const ComplexState._();
const factory ComplexState({
required String var1,
required double var2,
required bool var3,
required int var4,
}) = _ComplexState;
}
run this command in terminal
flutter pub run build_runner build --delete-conflicting-outputs
freezed will generate your ComplexState as well as some other useful methods like copyWith() which now you can use in your StateNotifier
Here is an example of using StateNotifier with Riverpod
final complexStateNotifierProvider = StateNotifierProvider(
(ref) {
const initialState = ComplexState(
var1: "var1",
var2: 90.0,
var3: true,
var4: 90,
);
return ComplexStateNotifier(initialState);
},
);
abstract class ComplexStateEvent {}
class ComplexStateEventDoSomething implements ComplexStateEvent {}
class ComplexStateNotifier extends StateNotifier<ComplexState> {
ComplexStateNotifier(ComplexState state) : super(state);
void onEvent(ComplexStateEvent event) {
if (event is ComplexStateEventDoSomething) {
state = state.copyWith(var1: "new value");
}
}
}
Note: freezed also support nullable and default constructor parameters which you may use for the initial state, read more on its pub.dev page
Related
How to access Riverpod ref from non-widget classes, before Riverpod 1, we could use context.read, where we had access to global navigation context. How can we achieve something similar in Riverpod 1?
I've tried using a ProviderContainer and it's working, but need to wrap the MaterialApp with UncontrolledProviderScope to be able to access the container, this in turn prevents logging changes of StateNotifierProviders, which is very handy while debugging.
Could anyone help with this?
You probably don't want to use ProviderContainer directly unless you're mocking dependencies for your non-widget class when testing. Pass your non-widget class a Reader tear-off from a widget, e.g.
class YourObject {
YourObject(this.reader);
Reader? reader;
void someFunc() {
final dependency = reader?.call(someProvider);
}
}
// Inside a consumer widget
final myObject = YourObject(ref.watch)
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 am learning flutter and it would be easy to understand if i get an example of listening to changes in dart only package aka riverpod.
I was using Provider in class where there is no stateless or stateful widget(basically no Buildcontext).
So i was passing context from other class to this class like this although it works but i feel its not right way.
class SaveToLocal {
final BuildContext context;
SaveToLocal(this.context);
foo(){
//used context here
var model = Provider.of<MyList>(context, listen: false);
}
}
then i came across riverpod where dart only package doesn't require context but i was confused as i was not able to find things like ProviderScope.I searched also examples but most of them are on flutter_riverpod not on riverpod.
This example on pub.dev is using FutureProvider what if i wanna just fetch from model
By model i mean
class MyList extends ChangeNotifier {
List<String> _myList = [];
List<String> get myList => _myList;
foo(){
//some task here
notifyListeners();
}
So here are questions
1.Is this appropriate way of using context
2.Is there any other way that i can achieve this
3.Can i get a simple example like CounterApp where read and watch is shown?
Sorry i got confused i thought riverpod is only for dart only classes but it was for non-flutter apps(saying after reading reso coder's article), as i tried further with flutter_riverpod and used without context (as said by Mr Random)and worked fine.
class SaveToLocal {
final container = ProviderContainer();
foo(){
var model = container.read(listProvider);
}
For the first question the answer is no: context shouldn't be saved in some external class since it might lead to issues.
I link you these example of how to manage state properly across the application:
https://medium.com/codechai/provider-state-management-in-flutter-d453e73537c5
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.
What I want
I have a simple model. The model extends from ChangeNotifier. If the ChangeNotifier calls notifyListeners() I want to "do" something like showing a SnackBar or Dialog. I provide the model with the Provider package to my widget tree.
What is it comparable to?
I used the flutter_bloc package before. This package offers BlocListener. With BlocListener I can "do" something on state changes. Example code:
BlocListener<BlocA, BlocAState>(
listener: (context, state) {
// do stuff here based on BlocA's state
},
child: Container(),
)
In the above example, the child will not rebuild but I can still do something depending on the state.
Is there anything comparable to the provider package? I read in the documentation of the package that ListenableProvider would give more freedom to do stuff like "animations". But I do not know if I can use this Provider in some way to show a snack bar on a notify.
Edit: I asked Remi, the author of Provider, on Twitter. With a short amount of characters, he told me that I can use didChangeDependencies for this behavior.
Please be cautious about using didChangeDependencies for this. There are only a few circumstances where didChangeDepdnencies can be used for this, and https://github.com/flutter/flutter/pull/49527 will make it impossible even in those.
The basic problem is that didChangeDepdnencies is sometimes (or, after #49527, always) called at a point where the tree is locked against state changes. Before the pull request, it is only safe on calls that are:
Not the first time (which is called from within a build scope)
Not the time where an element is being deactivated and eventually unmounted, so is no longer in a valid spot in the tree (this call will no longer happen at all after the pull request).
A safer way to do this is:
#override
void didChangeDependencies() {
super.didChangeDependencies();
if (Provider.of(context).whatever == someCondition) {
SchedulerBinding.instance.addPostFrameCallback(() {
// show modal or dialog
});
}
}
This code is safer to use because it is guaranteed to run at a point where state is safe to change in the tree, rather than only working in some very specific scenarios without the frame callback.
There are probably more elegant solutions than this (such as adding a callback directly on notifyListeners for your ChangeNotifier, assuming it only fires when the tree is in a mutable state).
try using listener on your provider
final myNotifier = context.read<MyNotifier>();
void listener() {
// Do something
}
myNotifier.addListener(listener);