5

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?

SouthbayDev
  • 698
  • 5
  • 10

1 Answers1

7

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

  1. add freezed_annotation to your dependencies
  2. add freezed and build_runner to your dev_dependencies
  3. 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;
}
  1. run this command in terminal
flutter pub run build_runner build --delete-conflicting-outputs
  1. 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

Ayoub
  • 493
  • 3
  • 8
  • Your welcome, a little upvote if it helped you would be much appreciated – Ayoub Jun 03 '22 at 14:14
  • Thanks for your answer! Maybe I wasn't precise enough about what I mean with "complex". Cloning is not a problem, when you only have "cheap" structure like only a handful of fields. But usually when you have data-rich apps, you have LOTS of fields lets say 50 and also deeper obeject-structures, that are expensive to rebuild. Let's say the user selects a product-category that leads to the state building up an object tree. This data structure is then also be used by other parts of the app. You can't clone that, because it is simply to expensive. – SouthbayDev Jun 03 '22 at 14:18
  • 1
    in such case, i think you have to do 2 things, first split your state, lets say you have a screen contains list of product categories and in the selected one, you have list products with quantity and more details, you need to split your state into something like CategoryState, ProductState ... and so on, this way you don't need to trigger an expensive build, you will build only the parts needed. 2nd you need to structure your actors upon each state, use clean architecture usecases, to manage your logic much better. – Ayoub Jun 03 '22 at 17:52
  • 1
    Yes, that's possible for some of the Widgets. But splitting up data fields that belong together and adding a number of specialized Providers doesn't make the code any more readable. Thats means I have to accept cloning of all the fields OR using ChangeNotifier with the risk that it might become deprecated in Riverpod :-( – SouthbayDev Jun 04 '22 at 08:47