Marco Papula
Marco Papula

Reputation: 841

Flutter/Dart: Subclass a freezed data class

I am using the following plugin: https://pub.dev/packages/freezed

I want to subclass a freezed data class to provide additional functionality in my data layer. So I have my data class which looks like:

import 'dart:ui';
import 'package:freezed_annotation/freezed_annotation.dart';
part 'card.freezed.dart';

@freezed
abstract class Card with _$Card {
  factory Card({String text, Color color, List<String> categories}) = _Card;
}

Now I want to have this Card class as a super class to my CardModel so that the CardModel has access to the same fields, the copyWith method, value equality, ... But I have no Idea how to go about this. I am trying something like this:

import 'package:flutter/widgets.dart';
import 'package:growthdeck/features/card_deck/domain/entities/card.dart';

import '../../domain/entities/card.dart';

abstract class CardModel extends Card {
  factory CardModel.fromMap(Map<String, dynamic> card) => Card(
        text: card["text"],
        color: Color(int.parse(card['color'])),
        categories: card['categories'] as List<String>,
      );
}

Which throws the following error:

package:growthdeck/features/card_deck/data/models/card_model.dart 11:9  new CardModel.fromMap
test/features/card_deck/data/models/card_model_test.dart 13:23          main.<fn>

type '_$_Card' is not a subtype of type 'CardModel' in type cast

Is there any way to do this properly? My workaround would be to simply "wrap" the Card class inside the CardModel and provide a toCard() method which is not very elegant :S

Upvotes: 23

Views: 19988

Answers (7)

DarkNeuron
DarkNeuron

Reputation: 8711

Freezed 3.0 will support inheritance:

class Base {
  Base(String value);
}

@freezed
class Usual extends Base with _$Usual {
  Usual({int? a}) a = a ?? 0, super('value');
  final int a;
}

Source

Upvotes: 0

EzPizza
EzPizza

Reputation: 1160

Freezed uses sealed classes to model inheritance, if you have a closed set of subclasses (cmp. this section).

I'm not sure how one can add methods to those subclasses, but in your case, a subclass with just a static/factory method wouldn't make much sense anyway.

This should do it:

import 'dart:ui';

import 'package:freezed_annotation/freezed_annotation.dart';

part 'card.freezed.dart';

@freezed
sealed class Card with _$Card {
  const factory Card.model({
    required String text,
    required Color color,
    required List<String> categories,
  }) = CardModel;

  factory Card.fromMap(Map<String, dynamic> card) => CardModel(
        text: card["text"],
        color: Color(int.parse(card['color'])),
        categories: card['categories'] as List<String>,
      );
}

or if CardModel is the only subclass, I would just turn Card into an interface and use CardModel as the freezed class:

import 'dart:ui';

import 'package:freezed_annotation/freezed_annotation.dart';

part 'card.freezed.dart';

abstract interface class Card {
  String get text;
  Color get color;
  List<String> get categories;
}

@freezed
sealed class CardModel with _$CardModel implements Card {
  const factory CardModel({
    required String text,
    required Color color,
    required List<String> categories,
  }) = _CardModel;

  factory CardModel.fromMap(Map<String, dynamic> card) => CardModel(
        text: card["text"],
        color: Color(int.parse(card['color'])),
        categories: card['categories'] as List<String>,
      );
}

Upvotes: 0

Michał Dobi Dobrzański
Michał Dobi Dobrzański

Reputation: 2000

Unfortunately, the extends keyword is forbidden for freezed generated classes.

However, you can achieve something which resembles the inheritance hierarchy with available Dart tricks:

  1. Use implements as last keyword after class declaration like so:
class MyModel with _$MyModel implements BaseModel

2.Define your BaseModel class:

abstract class BaseModel {
    // an abstract method
    String callMe(final int value);
    // an abstract getter
    FilePath get useMe;
}   
  1. The third thing is to make abstract classes provide concrete methods (the ones with actual body to be shared across subclasses). Use an extension mechanism for that:
extension BaseModelExtensions on BaseModel {
  String callSharedBehaviourAndReuseMeInChildFreezedClasses(int parameter) {
      // your logic
      return "";
  }
}

Upvotes: 1

atreeon
atreeon

Reputation: 24197

Morphy supports inheritance https://pub.dev/packages/morphy as well as clean minimal class definitions.

@morphy
abstract class $Pet {
  String get name;

  int get age;
}

@morphy
abstract class $Cat implements $Pet {
  double get whiskerLength;
}

@morphy
abstract class $Dog implements $Pet {
  String get woofSound;
}

It also supports polymorphic copy with and to / from json.

main(){
  //alter common properties of different sub types
  var petsOlder = catsAndDogs.map((pet) => //
    pet.copyWith_Pet(age: Opt(pet.age + 1)));

  //convert different sub types to json
  var json = pets.map((pet) => pet.toJson_2()).toList();

  //convert json into different subtypes
  var catsAndDogs = json2.map((json) => Pet.fromJson(json)).toList();
}

(I am the developer of the package)

Upvotes: 6

Bobin
Bobin

Reputation: 445

You can use dart_mappable. Here is an example for inheritance.

Upvotes: 2

Beatle Refractor
Beatle Refractor

Reputation: 659

Freezed class subclass itself. You dont need to abstract it.

import 'dart:ui';
import 'package:freezed_annotation/freezed_annotation.dart';
part 'card.freezed.dart';

@freezed
class Card with _$Card {
 const factory Card.cardModel({String text, Color color, List<String> categories}) = _$CardModel;
}

Run build runner for above freeze will create

abstract class _$CardModel implements Card{}

which you can access by Card factory.

Upvotes: 0

Abdelghani Bekka
Abdelghani Bekka

Reputation: 684

Freezed doesn’t support inheritance at the moment so it’s recommended to use composition instead of inheritance as mentioned by the creator here:

https://github.com/rrousselGit/freezed/issues/464

And in the comments of the post.

Upvotes: 14

Related Questions