Create interface that contains Freezed class signatures so I can called freezed functions on my interfaces

1.1k views Asked by At

I'm trying to have a base Freezed interface which my app entity interfaces can extend so I can call the freezed functions on the interfaces. I've started the process here which seems to be working so far:

abstract class IUserRegistrationEntity<T> extends FreezedClass<T> {
  String get nickName;
  String get email;
  String get confirmEmail;
  String get password;
  String get confirmPassword;
}

abstract class FreezedClass<T> {
  T get copyWith;
  Map<String, dynamic> toJson();
}

freezed class:

import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:vepo/domain/user_registration/i_user_registration_entity.dart';

part 'user_registration_entity.freezed.dart';
part 'user_registration_entity.g.dart';

@freezed
abstract class UserRegistrationEntity with _$UserRegistrationEntity {
  @Implements.fromString(
      'IUserRegistrationEntity<\$UserRegistrationEntityCopyWith<IUserRegistrationEntity>>')
  const factory UserRegistrationEntity(
      {String nickName,
      String email,
      String confirmEmail,
      String password,
      String confirmPassword}) = _IUserRegistrationEntity;

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

But now I need to add the fromJson factory constructor to the interface. I think this may be what I'm looking for although I can't really tell how to implement it in my code:

 T deserialize<T extends JsonSerializable>(
    String json,
    T factory(Map<String, dynamic> data),
  ) {
    return factory(jsonDecode(json) as Map<String, dynamic>);
  }
You an then call it with:

var myValue = deserialize(jsonString, (x) => MyClass.fromJson(x));

Any help adding the fromJson to my freezed interface would be appreciated.

2

There are 2 answers

17
BeniaminoBaggins On BEST ANSWER

I've found a way to get the same benefits of programming to an interface or "abstraction" with freezed objects, while still getting to call those freezed functions:

@freezed
abstract class Person with _$Person {
  @With(BasicPersonMixin)
  const factory Person.basicPerson(
      {int? id, String? firstName, String? lastName}) = BasicPerson;

  @With(FancyPersonMixin)
  const factory Person.fancyPerson({String? firstName, required String extraPropMiddleName, String? lastName}) =
      FancyPerson;

  factory Person.fromJson(Map<String, dynamic> json) => _$PersonFromJson(json);

  const Person._();

  void functionThatEveryPersonShares() {
    print('I am a person');
  }

  String greet() {
    return 'override me with a mixin or abstract class';
  }
}

mixin FancyPersonMixin {

  String get extraPropMiddleName {
      return 'my default middle name is John`;
  }

  String greet() {
    return 'Salutations!';
  }

  void specialisedFunctionThatOnlyIHave() {
    print('My middle name is $extraPropMiddleName');
  }
}

mixin BasicPersonMixin {
  String greet() {
    return 'Hi.';
  }
}

Now we have 2 concrete classes: BasicPerson, and FancyPerson which are both a Person. Now we can program to Person throughout the app, and still call .copyWith and .fromJson and so on and so forth. The different types of Person can vary independently from each other by using mixins and still be used as a Person type. Works with generics etc (from docs - @With.fromString('AdministrativeArea<House>')) but I have kept the example simple for this question to most simply show the benefits. You can also make Person extend another base class.

0
BeniaminoBaggins On

I've found another way to let you be a bit more abstract than my other answer. Say you're in a highly abstract super-class, so you don't want to work with objects as specific as Person. You want to work with "a base freezed object"; just cast your type to dynamic in brackets and go ahead and use copyWith freely. Sure, it's not typesafe, but it's a worthy option if it allows you to do something in a super-class rather than in every sub-class.

mixin LocalSaveMixin<TEntity extends LocalSaveMixin<TEntity>> on Entity {
  LocalRepository<TEntity> get $repository;
  Ref? get provider;

  TEntity $localFetch() {
    return ($repository.$localFetch() as dynamic).copyWith(provider: provider)
        as TEntity;
  }

  TEntity $localSave() {
    return $repository.$localSave(entity: this as TEntity);
  }
}