2

In the following snippet the state.copyWith function is not available.

@freezed
class MyState with _$MyState {
  @JsonSerializable(fieldRename: FieldRename.snake, explicitToJson: true)
  const factory MyState({@Default(0) int counter,}) = _MyState;
  const factory MyState.initial({@Default(0) int counter}) = Initial;
  const factory MyState.loading() = Loading;
  const factory MyState.one() = One;
  const factory MyState.two() = Two;

  factory MyState.fromJson(Map<String, dynamic> json) =>
      _$MyStateFromJson(json);
}

class MyStateNotifier extends StateNotifier<MyState> {
  MyStateNotifier() : super(MyState.initial());

  Future<void> one() async {
    state = MyState.loading();
    await Future.delayed(Duration(seconds: 5));
    state.copyWith(counter: 1);
  }
}

However when I remove the sealed classes the copyWith function is available.

@freezed
class MyState with _$MyState {
  @JsonSerializable(fieldRename: FieldRename.snake, explicitToJson: true)
  const factory MyState({@Default(0) int counter,}) = _MyState;
  // const factory MyState.initial({@Default(0) int counter}) = Initial;
  // const factory MyState.loading() = Loading;
  // const factory MyState.one() = One;
  // const factory MyState.two() = Two;

  factory MyState.fromJson(Map<String, dynamic> json) =>
      _$MyStateFromJson(json);
}

class MyStateNotifier extends StateNotifier<MyState> {
  MyStateNotifier() : super(MyState());

  Future<void> one() async {
    await Future.delayed(Duration(seconds: 5));
    state.copyWith(counter: 1);
  }
}

What do I need to change to make the copyWith available in the first snippet?

ptheofan
  • 2,150
  • 3
  • 23
  • 36

2 Answers2

5

Only properties that are common to all constructors will generate a copyWith method, as mentioned in the README docs.

We can use copyWith with properties defined on all constructors...

Imagine you had an instance of Loading, what copyWith method would you expect that to have? It has no properties, hence it can't have any copyWith methods, therefore the union of all types also can't.

However, you can use pattern matching to call copyWith on the instances of the right type.

In your example, something like this would work:

MyState myState = ...;

myState.maybeMap(
    initial: (v: Initial) => v.copyWith(counter: v.counter + 1),
    orElse: () => ...,
);

Or using when:

MyState myState = ...;

myState.when(
    (int counter) => MyState.initial(counter + 1),
    loading: () => loading,
    one: () => one,
    two: () => two,
);
Kyle Hayes
  • 5,225
  • 8
  • 38
  • 53
Renato
  • 12,940
  • 3
  • 54
  • 85
  • Adding the counter to all constructors works great. However, I need to supply it every time on every call, for every property that is added as the code will grow. This would make it extremely painful to work with. Is there some way of automating the copy/paste of the common properties from 1 state to the other? Something like ```state = MyState.loading(copyFromExistingStateAllCommonProperties);``` – ptheofan Jun 05 '21 at 11:10
  • Why would you have a `counter` on `Loading` though? It doesn't seem to make sense. – Renato Jun 05 '21 at 11:17
  • Maybe add an extension function to `MyState` that returns `0` for any type that doesn't have the property? – Renato Jun 05 '21 at 11:18
  • Posted a more specific question re this here if anyone has a moment https://stackoverflow.com/questions/69783285/freezed-copywith-method-not-avaliable-for-listt-model-in-union-sealed-using – Greg Oct 31 '21 at 02:27
2

you can access the copyWith method, do Type conversion:

`if (state is CurrentState) { final myState = state as CurrentState }

myState.copyWith (...)`

Marius Ruica
  • 441
  • 4
  • 5