dart_mappable icon indicating copy to clipboard operation
dart_mappable copied to clipboard

Enums don't work - Unknown type

Open Jakemangan opened this issue 1 year ago • 5 comments

Honestly can't figure this out and your docs really aren't clear. Dart mappable is continuously saying it doesn't know what my enum is when I try to run UserSettings.toMap

Image

User settings immediately before map

Image

I've tried BOTH ValuesMode.indexed and ValuesMode.named, no difference

It's as if by using @MappableField on my enum field it's not making the link between the declared MappableEnum in the Enum file and it's trying to use a different map but can't find anything

If I don't include @MappableField above UserSettings.currentFocusFilter then the code generation adds FieldMode.member and ignores it entirely and doesn't serialise it

DEFINITIONS

I have a class defined as below with an enum field FocusFilter

@MappableClass()
class UserSettings with UserSettingsMappable {
  @MappableField(hook: UserSettingsStateDateChoiceHook())
  UserSettingsStateDateChoice currentFocusDate = UserSettingsStateDateChoice(DateTime.now(), false);

  @MappableField()
  FocusFilter currentFocusFilter = FocusFilter.all;

  @MappableField()
  bool hideStreaks = false;

  @MappableField()
  bool hideFocusRings = false;

  @MappableField()
  bool focusWallShowDates = false;

  @MappableField()
  FocusWallStyleTypeEnum focusWallStyleType = FocusWallStyleTypeEnum.bulletJournal;

  @MappableField()
  PlanType? planType = PlanType.free;

  List<OnboardingQuestionnaireAreas> onboardingQuestionnaireAnswers = [];

  @MappableField()
  bool thankYouSheetDisplayed = false;

  @MappableField()
  int maxFocusCount = 3;
}

My enum is defined as

part 'focus_filter_enum.mapper.dart';

@MappableEnum()
enum FocusFilter { all, monthly, weekly, daily, uncompleted }

I've ran build_runner, which has produced the following for UserSettings and FocusFilter, there's extra code below because I've removed the excess fields in UserSettings

USER SETTINGS GENERATED CODE

// coverage:ignore-file
// GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: type=lint
// ignore_for_file: unused_element, unnecessary_cast, override_on_non_overriding_member
// ignore_for_file: strict_raw_type, inference_failure_on_untyped_parameter

part of 'user_settings.dart';

class UserSettingsMapper extends ClassMapperBase<UserSettings> {
  UserSettingsMapper._();

  static UserSettingsMapper? _instance;
  static UserSettingsMapper ensureInitialized() {
    if (_instance == null) {
      MapperContainer.globals.use(_instance = UserSettingsMapper._());
    }
    return _instance!;
  }

  @override
  final String id = 'UserSettings';

  static UserSettingsStateDateChoice _$currentFocusDate(UserSettings v) =>
      v.currentFocusDate;
  static const Field<UserSettings, UserSettingsStateDateChoice>
      _f$currentFocusDate = Field('currentFocusDate', _$currentFocusDate,
          hook: UserSettingsStateDateChoiceHook());
  static FocusFilter _$currentFocusFilter(UserSettings v) =>
      v.currentFocusFilter;
  static const Field<UserSettings, FocusFilter> _f$currentFocusFilter =
      Field('currentFocusFilter', _$currentFocusFilter);
  static bool _$hideStreaks(UserSettings v) => v.hideStreaks;
  static const Field<UserSettings, bool> _f$hideStreaks =
      Field('hideStreaks', _$hideStreaks);
  static bool _$hideFocusRings(UserSettings v) => v.hideFocusRings;
  static const Field<UserSettings, bool> _f$hideFocusRings =
      Field('hideFocusRings', _$hideFocusRings);
  static bool _$focusWallShowDates(UserSettings v) => v.focusWallShowDates;
  static const Field<UserSettings, bool> _f$focusWallShowDates =
      Field('focusWallShowDates', _$focusWallShowDates);
  static FocusWallStyleTypeEnum _$focusWallStyleType(UserSettings v) =>
      v.focusWallStyleType;
  static const Field<UserSettings, FocusWallStyleTypeEnum>
      _f$focusWallStyleType = Field('focusWallStyleType', _$focusWallStyleType);
  static PlanType? _$planType(UserSettings v) => v.planType;
  static const Field<UserSettings, PlanType> _f$planType =
      Field('planType', _$planType);
  static List<OnboardingQuestionnaireAreas> _$onboardingQuestionnaireAnswers(
          UserSettings v) =>
      v.onboardingQuestionnaireAnswers;
  static const Field<UserSettings, List<OnboardingQuestionnaireAreas>>
      _f$onboardingQuestionnaireAnswers = Field(
          'onboardingQuestionnaireAnswers', _$onboardingQuestionnaireAnswers,
          mode: FieldMode.member);
  static bool _$thankYouSheetDisplayed(UserSettings v) =>
      v.thankYouSheetDisplayed;
  static const Field<UserSettings, bool> _f$thankYouSheetDisplayed =
      Field('thankYouSheetDisplayed', _$thankYouSheetDisplayed);
  static int _$maxFocusCount(UserSettings v) => v.maxFocusCount;
  static const Field<UserSettings, int> _f$maxFocusCount =
      Field('maxFocusCount', _$maxFocusCount);

  @override
  final MappableFields<UserSettings> fields = const {
    #currentFocusDate: _f$currentFocusDate,
    #currentFocusFilter: _f$currentFocusFilter,
    #hideStreaks: _f$hideStreaks,
    #hideFocusRings: _f$hideFocusRings,
    #focusWallShowDates: _f$focusWallShowDates,
    #focusWallStyleType: _f$focusWallStyleType,
    #planType: _f$planType,
    #onboardingQuestionnaireAnswers: _f$onboardingQuestionnaireAnswers,
    #thankYouSheetDisplayed: _f$thankYouSheetDisplayed,
    #maxFocusCount: _f$maxFocusCount,
  };

  static UserSettings _instantiate(DecodingData data) {
    return UserSettings();
  }

  @override
  final Function instantiate = _instantiate;

  static UserSettings fromMap(Map<String, dynamic> map) {
    return ensureInitialized().decodeMap<UserSettings>(map);
  }

  static UserSettings fromJson(String json) {
    return ensureInitialized().decodeJson<UserSettings>(json);
  }
}

mixin UserSettingsMappable {
  String toJson() {
    return UserSettingsMapper.ensureInitialized()
        .encodeJson<UserSettings>(this as UserSettings);
  }

  Map<String, dynamic> toMap() {
    return UserSettingsMapper.ensureInitialized()
        .encodeMap<UserSettings>(this as UserSettings);
  }

  UserSettingsCopyWith<UserSettings, UserSettings, UserSettings> get copyWith =>
      _UserSettingsCopyWithImpl(this as UserSettings, $identity, $identity);
  @override
  String toString() {
    return UserSettingsMapper.ensureInitialized()
        .stringifyValue(this as UserSettings);
  }

  @override
  bool operator ==(Object other) {
    return UserSettingsMapper.ensureInitialized()
        .equalsValue(this as UserSettings, other);
  }

  @override
  int get hashCode {
    return UserSettingsMapper.ensureInitialized()
        .hashValue(this as UserSettings);
  }
}

extension UserSettingsValueCopy<$R, $Out>
    on ObjectCopyWith<$R, UserSettings, $Out> {
  UserSettingsCopyWith<$R, UserSettings, $Out> get $asUserSettings =>
      $base.as((v, t, t2) => _UserSettingsCopyWithImpl(v, t, t2));
}

abstract class UserSettingsCopyWith<$R, $In extends UserSettings, $Out>
    implements ClassCopyWith<$R, $In, $Out> {
  $R call();
  UserSettingsCopyWith<$R2, $In, $Out2> $chain<$R2, $Out2>(Then<$Out2, $R2> t);
}

class _UserSettingsCopyWithImpl<$R, $Out>
    extends ClassCopyWithBase<$R, UserSettings, $Out>
    implements UserSettingsCopyWith<$R, UserSettings, $Out> {
  _UserSettingsCopyWithImpl(super.value, super.then, super.then2);

  @override
  late final ClassMapperBase<UserSettings> $mapper =
      UserSettingsMapper.ensureInitialized();
  @override
  $R call() => $apply(FieldCopyWithData({}));
  @override
  UserSettings $make(CopyWithData data) => UserSettings();

  @override
  UserSettingsCopyWith<$R2, UserSettings, $Out2> $chain<$R2, $Out2>(
          Then<$Out2, $R2> t) =>
      _UserSettingsCopyWithImpl($value, $cast, t);
}

FOCUS FILTER GENERATED CODE

// coverage:ignore-file
// GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: type=lint
// ignore_for_file: unused_element, unnecessary_cast, override_on_non_overriding_member
// ignore_for_file: strict_raw_type, inference_failure_on_untyped_parameter

part of 'focus_filter_enum.dart';

class FocusFilterMapper extends EnumMapper<FocusFilter> {
  FocusFilterMapper._();

  static FocusFilterMapper? _instance;
  static FocusFilterMapper ensureInitialized() {
    if (_instance == null) {
      MapperContainer.globals.use(_instance = FocusFilterMapper._());
    }
    return _instance!;
  }

  static FocusFilter fromValue(dynamic value) {
    ensureInitialized();
    return MapperContainer.globals.fromValue(value);
  }

  @override
  FocusFilter decode(dynamic value) {
    switch (value) {
      case 'all':
        return FocusFilter.all;
      case 'monthly':
        return FocusFilter.monthly;
      case 'weekly':
        return FocusFilter.weekly;
      case 'daily':
        return FocusFilter.daily;
      case 'uncompleted':
        return FocusFilter.uncompleted;
      default:
        throw MapperException.unknownEnumValue(value);
    }
  }

  @override
  dynamic encode(FocusFilter self) {
    switch (self) {
      case FocusFilter.all:
        return 'all';
      case FocusFilter.monthly:
        return 'monthly';
      case FocusFilter.weekly:
        return 'weekly';
      case FocusFilter.daily:
        return 'daily';
      case FocusFilter.uncompleted:
        return 'uncompleted';
    }
  }
}

extension FocusFilterMapperExtension on FocusFilter {
  String toValue() {
    FocusFilterMapper.ensureInitialized();
    return MapperContainer.globals.toValue<FocusFilter>(this) as String;
  }
}

Jakemangan avatar Feb 27 '25 14:02 Jakemangan

Image

Seriously why is this a thing and a requirement when I've stated that the indexed version of the enum should be used

And if it's an absolute hard requirement why on earth isn't it mentioned in the docs?

Jakemangan avatar Feb 27 '25 15:02 Jakemangan

Image

Makes no difference actually, still fails regardless

Jakemangan avatar Feb 27 '25 15:02 Jakemangan

You need to add a constructor to UserSettings

schultek avatar Feb 27 '25 17:02 schultek

Though this still might be a bug that the builder doesn't pick up the enum mapper.

schultek avatar Feb 27 '25 17:02 schultek

Same here, but perhaps for another reason:

I have this mapper to map enums that I don't own:

final class CustomEnumMapper<T extends Enum> extends EnumMapper<T> {
  const CustomEnumMapper({required this.enumValues});

  final List<T> enumValues;

  @override
  T decode(Object value) {
    if (value is! String) {
      throw MapperException.unexpectedType(
        value.runtimeType,
        (String).runtimeType.toString(),
      );
    }

    return enumValues.firstWhere((v) => v.name == value);
  }

  @override
  Object? encode(T self) {
    return self.name;
  }
}

Then, I use this on main:

MapperContainer.globals.useAll([
  const BoolMapper(), // 0 or 1
  const ColorMapper(), // Color to #Hex
  const DateMapper(), // A const Date class
  CustomEnumMapper(enumValues: ThemeMode.values),
  CustomEnumMapper(enumValues: DynamicSchemeVariant.values),
]);

When I try to serialize/deserialize something with ThemeMode, I get the same exception.

Debugging the code where the mapper is taken from _mappers (mapper_container.dart, line 300), I see only one CustomEnumMapper:

Image

So, I guess it is overriding the CustomEnumMapper and only the last one added remains.

EDIT:

After making the CustomEnumMapper abstract and adding those:

final class ThemeModeEnumMapper extends CustomEnumMapper<ThemeMode> {
  const ThemeModeEnumMapper() : super(enumValues: ThemeMode.values);
}

final class DynamicSchemeVariantEnumMapper
    extends CustomEnumMapper<DynamicSchemeVariant> {
  const DynamicSchemeVariantEnumMapper()
    : super(enumValues: DynamicSchemeVariant.values);
}

Now, the correct mappers are added:

Image

JCKodel avatar Nov 20 '25 03:11 JCKodel