How do I make a Freezed object take a generic type? I want to do this:
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:vepo/src/entity_types/option_entity.dart';
part 'vegan_item_tag.freezed.dart';
part 'vegan_item_tag.g.dart';
@freezed
abstract class VeganItemTag<T>
with _$VeganItemTag<T>
implements OptionEntity<T> {
const factory VeganItemTag({int? iconCodePoint, T? id, String? name}) =
_VeganItemTag;
const VeganItemTag._();
factory VeganItemTag.fromJson(Map<String, dynamic> json) =>
_$VeganItemTagFromJson(json);
}
I've tried using @With.fromString('AdministrativeArea<House>')
from the docs but can't apply it correctly to this class.
One of the errors:
lib/src/common/enums/tags/common/vegan_item_tag.freezed.dart:142:32: Error: Too few positional arguments: 2 required, 1 given.
$$_VeganItemTagFromJson(json);
Think I might be on the right track with this, but it no longer generates a vegan_item_tag.g.dart
file:
@freezed
abstract class VeganItemTag<T>
with _$VeganItemTag<T>
implements OptionEntity<T> {
const factory VeganItemTag(
{required int iconCodePoint,
required T id,
required String name}) = _VeganItemTag;
const VeganItemTag._();
factory VeganItemTag.fromJson(
Map<String, Object?> json,
T Function(Object?) fromJsonT,
) => VeganItemTag(
iconCodePoint: json['iconCodePoint'] as int,
id: fromJsonT(json['id']),
name: json['name'] as String,
);
}
There are several solutions to this problem. But in all of them you need to explicitly convert your classes to a generic type Firebase can handle such as String
or Map<dynamic, String>
.
The 3 ways to implement such behavior are:
This is messier to maintain than JsonConverters
on complex scenarios so I would discard this option for your solution.
It works for automatizing conversions of specific classes or abstract classes through inheritance, but from generic types with different data to store it may not be what you require. If you are always saving the same values from the generic type T
you may try to use this solution through implemented abstract classes.
This is what you are actually asking about. Working with genericArgumentFactories on json_serializable and Freezed is not easy and I found a bug on Freezed package meanwhile.
But I managed to get this code working which is the actual solution ðŸ§.
@freezed
@JsonSerializable(genericArgumentFactories: true)
class VeganItemTagV2<T> with _$VeganItemTagV2<T> {
const VeganItemTagV2._();
const factory VeganItemTagV2({
required int iconCodePoint,
required T id,
required String name,
}) = _VeganItemTag<T>;
//It only works with block bodies and not with expression bodies
//I don't know why
factory VeganItemTagV2.fromJson(
Map<String, dynamic> json, T Function(Object? json) fromJsonT) {
return _$VeganItemTagV2FromJson<T>(json, fromJsonT);
}
Map<String, dynamic> toJson(Object Function(T value) toJsonT) {
return _$VeganItemTagV2ToJson<T>(this, toJsonT);
}
}
This adds the converters on the toJson
and fromJson
methods to be used depending on the generic type.
NOTE. These methods can't be expressions for some bug as it doesn't compile but it works with block bodies. Freezed does not oficcially support it so you may consider creating this class without Freezed package.
This is a example with an encapsulated class of a String
and a test class to see how it works:
class VeganId {
final String id;
VeganId(this.id);
String itemId() {
return id;
}
@override
String toString() {
return 'VeganId{id: $id}';
}
@override
bool operator ==(Object other) =>
identical(this, other) ||
other is VeganId && runtimeType == other.runtimeType && id == other.id;
@override
int get hashCode => id.hashCode;
}
And the test which works fine
test('veganItemV2 from and toJson', () {
final dto = VeganItemTagV2<VeganId>(
iconCodePoint: 1,
id: VeganId("veganID"),
name: "name",
);
final Map<String, dynamic> actualToJson = dto.toJson((id) => id.itemId());
expect(actualToJson, {"iconCodePoint": 1, "id": "veganID", "name": "name"});
final VeganItemTagV2 actualFromJson = VeganItemTagV2<VeganId>.fromJson(
actualToJson,
(json) =>
VeganId(json as String),
);
expect(actualFromJson, dto);
});