Search code examples
dartfreezed

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


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.


Solution

  • 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.