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
dependencies
dev_dependencies
complex_state.dart
something like this, change the factory constructor to match your needsimport '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;
}
flutter pub run build_runner build --delete-conflicting-outputs
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