GregoryConrad / rearch-dart

Re-imagined approach to application design and architecture
https://pub.dev/packages/rearch
MIT License
92 stars 4 forks source link

Implementing a StateNotifier-like class with rearch #69

Closed alfalcon90 closed 10 months ago

alfalcon90 commented 10 months ago

For some of the more complex parts of my projects I often use state_notifier and freezed data classes for grouping methods and state under their own respective classes. For example:

class City {
  City({required this.name, required this.population});
  final String name;
  final int population;
}

@freezed
class CityState with _$CityState {
  const factory CityState({
    required List<City> cities,
  }) = _SafetyState;

  factory CityState.initial() => const CityState(
        reports: List.empty(),
      );
}

class CityNotifier extends StateNotifier<CityState> {
  CityNotifier() : super(const CityState.initial());

  void addCity(City newCity) {
    state = state.copyWith(cities: [...state, newCity]);
  }
}

I tried looking through the documentation but couldn't find an example of using rearch for building something like this. The class below is my quick and dirty initial stab at accomplishing a similar thing but I could be way off. Either some official recommended way of doing this in the documentation or a RearchNotifier class that implements it the right way would be great.

class RearchNotifier<State> {
  final _container = CapsuleContainer();

  State build() => throw UnimplementedError();

  (State, void Function(State)) _state(CapsuleHandle use) => use.state(build());

  State get state => _container.read((CapsuleHandle use) => _state(use).$1);

  set state(State value) => _container.read((CapsuleHandle use) {
        final state = _state(use);
        state.$2(value);
      });

  Capsule<R> map<R>(R Function(State s) mapper) =>
      _state.map(((State, void Function(State)) s) => mapper(s.$1));

  void reset() => _container.read((CapsuleHandle use) {
        final state = _state(use);
        state.$2(build());
      });
}

// Which then is used like this
class CityNotifier extends RearchNotifier<CityState> {
  @override
  CityState build() => CityState.initial();

  void addCity(City newCity) {
    state = state.copyWith(cities: [...state, newCity]);
  }
}
GregoryConrad commented 10 months ago

Hi again, finally got to my laptop.

Freezed plays wonderfully with ReArch, but definitely do not use notifiers in conjunction with ReArch. The equivalent is just a capsule with a different API exposed like:

(List<City>, {void Function(City) addCity,}) citiesManager(CapsuleHandle use) {
  final (cities, setCities) = use.state(<City>[]);
  return (
    cities,
    addCity: (city) => setCities([...cities, city]),
    // any other functions you would normally put in your notifier...
  );
}

You can add any other functions to the return value of the capsule as you see fit.

I also would suggest using fast_immutable_collections in the case of immutable lists here. It is much faster and much more ergonomic when handling scenarios like this.

Let me know if you have any other questions!