schultek / dart_mappable

Improved json serialization and data classes with full support for generics, inheritance, customization and more.
https://pub.dev/packages/dart_mappable
MIT License
165 stars 23 forks source link

`copyWith` method fails to include base class parameters in inheritance #216

Closed meynety closed 1 month ago

meynety commented 3 months ago

Use case

Hi there ! I’m trying to use the copyWith method in a Dart class hierarchy that involves inheritance. I’m not sure if I'm encountering a real issue or if I'm misunderstanding something. Apologies if it's the latter!

(Edit : Issue is similar to https://github.com/schultek/dart_mappable/issues/215#issuecomment-2260235384 but I believe that the use case is different enough to need a different solution.)

Here's the setup I'm working with:

@MappableClass()
class State with StateMappable {
  final bool aMember;

  State({required this.aMember});
}

@MappableClass()
class InitialState extends State with InitialStateMappable {
  InitialState() : super(aMember: true);
}

void main() {
  State state = InitialState(aMember: true);
  state.copyWith(aMember: false);
}

Expected behavior

I expect the copyWith method to include all parameters from the base class constructor. In this example, it should include aMember.

Actual Issue

The generated copyWith method does not include aMember as a parameter.

Here is a screenshot of the issue :

image

Here is a test project that recreates this issue : dart-mappable-copy-with.zip

Findings and Workarounds

  1. If I add aMember to the InitialState constructor, the copyWith method works as expected. However, this defeats the purpose of having InitialState with a default value for aMember.
@MappableClass()
class InitialState extends State with InitialStateMappable {
  // Adding the param to the constructor makes the copyWith work, but defeats the purpose of the class
  InitialState({required super.aMember});
}

void main() {
  State state = InitialState(aMember: true);
  state.copyWith(aMember: false); // Works ✅ 
}
  1. It seems that all subclasses must explicitly declare the aMember parameter in their constructors for copyWith to function correctly. If I add another subclass without aMember, the copyWith method fails again :

@MappableClass()
class OtherState extends State with OtherStateMappable {
  final String otherMember;
  OtherState({required this.otherMember}) : super(aMember: false);

  // Adding aMember to this constructor also makes copyWith work
  // OtherState({required this.otherMember, required super.aMember});
}

void main() {
  State state = InitialState(aMember: true);
  state.copyWith(aMember: false); // Does not work ❌ "The named parameter 'aMember' isn't defined."
}

Questions:

schultek commented 1 month ago

Hi, yes you found the right comment: https://github.com/schultek/dart_mappable/issues/215#issuecomment-2260235384

This is the same case. I would like to improve this, but its a limitation created by the inheritance structure that I cannot fix. All subclasses must have a parameter in their constructor for it to be usable in the superclasses copyWith.

I think the best workaround in your case is to make it a optional parameter with a default value:

class InitialState extends State with InitialStateMappable {
  InitialState({super.aMember = false});
}