Flutter Freezed - Добавить универсальное поле в замороженный класс

Как заставить замороженный объект иметь универсальный тип? Я хочу сделать это:

      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);
}

Я пробовал использовать @With.fromString('AdministrativeArea<House>') из документации, но не может правильно применить его к этому классу.

Некоторые ошибки консоли находятся в комментариях, и я также вижу эту ошибку, когда делаю это:

      <GroceryItemTagEnum, VeganItemTag<GroceryItemTagEnum>>{
    GroceryItemTagEnum.babyAndChild: VeganItemTag<GroceryItemTagEnum>(
        name: 'Baby & Child',
        id: GroceryItemTagEnum.babyAndChild,
        iconCodePoint: 0xf77c),

Тип элемента _ $ _ VeganItemTag<\ dynamic> нельзя присвоить типу значения карты VeganItemTag<\ GroceryItemTagEnum>

2 ответа

Есть несколько вариантов решения этой проблемы. Но во всех из них вам нужно явно преобразовать свои классы в общий тип, который Firebase может обрабатывать, например или Map<dynamic, String>.

Три способа реализовать такое поведение:

FromJson ToJson

Это сложнее поддерживать, чем JsonConverters в сложных сценариях, поэтому я бы отказался от этого варианта для вашего решения.

JsonConverters

Он работает для автоматизации преобразований определенных классов или абстрактных классов через наследование, но из общих типов с разными данными для хранения это может быть не то, что вам нужно. Если вы всегда сохраняете одни и те же значения из универсального типа T вы можете попробовать использовать это решение через реализованные абстрактные классы.

GenericArgumentFactories

Это то, о чем вы на самом деле спрашиваете. Работать с genericArgumentFactories на json_serializable и непросто, и тем временем я обнаружил ошибку в Freezedпакете Freezed .

Но мне удалось заставить этот код работать, что является фактическим решением 🧭.

      @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);
  }
}

Это добавляет конвертеры на toJson а также fromJson методы, которые будут использоваться в зависимости от универсального типа.

ПРИМЕЧАНИЕ. Эти методы не могут быть выражениями для какой-либо ошибки, поскольку она не компилируется, но работает с телами блоков. Freezed официально не поддерживает его, поэтому вы можете рассмотреть возможность создания этого класса без пакета Freezed.

Это пример инкапсулированного класса String и тестовый класс, чтобы увидеть, как это работает:

      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;
}

И тест, который отлично работает

        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);
  });

Ваш последний код не генерирует vegan_item_tag.g.dart потому что вы написали неправильный код в VeganItemTag.fromJsonфабрика. Отредактируйте его примерно так:

      factory VeganItemTag.fromJson(
    Map<String, Object?> json,
    T Function(Object?) fromJsonT,
  ) => _$VeganItemTagFromJson(json, fromJsonT);

Или же:

      factory VeganItemTag.fromJson(Map<String, Object?> json) =>
    _$VeganItemTagFromJson(json);

А затем повторно запустите flutter pub run build_runner build --delete-conflicting-outputs команда.

Другие вопросы по тегам