I can't seem to understand how to make abstract classes and their subclasses work properly with Freezed. Specifically, I need to define an abstract super-class with some to-override getters; the subclasses will need to override the getters, while exploiting freezed to generate their boilerplate code.
I do understand what the documentation suggests, but I do need more.
I created a repo to show off what I need, in case you want to cut to the chase. All I need are these tests to pass and work as intended.
I need a clean, readable and maintainable way to do this.
In the repo you'll find a small reproduction case:
Base
abstract class;A
, which extends the Base
class (I wanted to simplify things, but I do have more subclasses in my real use case, say X
, Y
, Z
, etc.);A
needs to be implemented with Freezed, with JSON serialization / deserialization;Recap: Base
is there to have write common ground between A
, X
, Y
and Z
; as mentioned above, Freezed is a must-have for these subclasses.
My goal is to exploit composition.
Let a class B
have a Base get myValue
getter, with Freezed. Unsurprisingly, I want to interact with this value by accessing its copyWith
method (or others, like toJson
), but this gets complicated quite fast (see Problem 1).
Again, read the tests for the desired outcome.
Implementing what I've described above, while it makes sense, is no easy task (to my understanding).
For example, the following:
abstract class Base {
const Base();
Function copyWith();
Map<String, dynamic> toJson();
String get id;
}
won't work because the subclasses will feel ambiguity onto which superclass method to use (error here): should it use the one generated by @freezed
, or the abstract one? That's a compile-time error.
I have no clue how to properly write a contract while exploiting Freezed.
At first, build_runner
rightfully complains as it can't generate the fromJson
method onto B
because Base
has no @JsonSerializable
method.
This would imply implementing a converter manually: I did so, but this gets old quickly. This implies to re-write the converter for each new subclass created and raise an exception for a subclass that isn't handled, yet (that's a strong code smell).
Such atrocity is found in this file.
How can achieve this in a clean way?
After two days of work and research, I managed to get out of this mess.
freezed
for now (I'll just keep it for the union type system);I'll post here a quick pseudocode implementation achievable with dart_mappable
:
@MappableClass()
abstract class MyBase with MyBaseMappable {
const MyBase(this.id);
final String id;
}
@MappableClass()
class A extends MyBase with AMappable {
const A(super.id);
}
@MappableClass()
class B with BMappable {
const B(this.value);
final MyBase value;
}
void main() {
const a = A('hello');
const b = B(a);
print(b.value.copyWith(id: "lol")); // Yes!
}
This requires v2 of dart_mappable
to work, which is currently in a pre-release state.