schultek / dart_mappable

Improved json serialization and data classes with full support for generics, inheritance, customization and more.
https://pub.dev/packages/dart_mappable
MIT License
145 stars 22 forks source link

InvalidType when using a class that has a Color (or Offset) typed field. #142

Closed point-source closed 6 months ago

point-source commented 10 months ago

I have this class:

@MappableClass()
@immutable
class EdgeUi with EdgeUiMappable {
  final String label;
  final Color color;

  const EdgeUi({
    this.label = '',
    this.color = Colors.blue,
  });
}

Here is the generated output:

class EdgeUiMapper extends ClassMapperBase<EdgeUi> {
  EdgeUiMapper._();

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

  @override
  final String id = 'EdgeUi';

  static String _$label(EdgeUi v) => v.label;
  static const Field<EdgeUi, String> _f$label =
      Field('label', _$label, opt: true, def: '');
  static InvalidType _$color(EdgeUi v) => v.color;
  static const Field<EdgeUi, InvalidType> _f$color =
      Field('color', _$color, opt: true, def: Colors.blue);

  @override
  final Map<Symbol, Field<EdgeUi, dynamic>> fields = const {
    #label: _f$label,
    #color: _f$color,
  };

  static EdgeUi _instantiate(DecodingData data) {
    return EdgeUi(label: data.dec(_f$label), color: data.dec(_f$color));
  }

  @override
  final Function instantiate = _instantiate;

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

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

Noticed that these lines contain "InvalidType":

  static InvalidType _$color(EdgeUi v) => v.color;
  static const Field<EdgeUi, InvalidType> _f$color =
      Field('color', _$color, opt: true, def: Colors.blue);

Even though I don't believe this affects the code generation step, my main method looks like this:

void main() {
  initializeMappers();

  /// Initialize custom mappers
  MapperContainer.defaults.useAll([
    ColorMapper(),
  ]);

  runApp(const ProviderScope(child: MyApp()));
}

And here is the color mapper:

/// Enables dart_mappable to serialize/deserialize color data
class ColorMapper extends SimpleMapper<Color> {
  @override
  Color decode(value) => value is int
      ? Color(value)
      : throw Exception(
          'Expected value of type int but got ${value.runtimeType} instead',
        );

  @override
  encode(Color? self) => self?.value;
}

Fwiw, this looks very similar to issue #102

Things I've tried:

Version info: Flutter 3.13.8 • channel stable • https://github.com/flutter/flutter.git Framework • revision 6c4930c4ac (6 days ago) • 2023-10-18 10:57:55 -0500 Engine • revision 767d8c75e8 Tools • Dart 3.1.4 • DevTools 2.25.0 macOS 14.0 dart_mappable: ^4.0.1 dart_mappable_builder: ^4.0.1

point-source commented 10 months ago

This also is happening on any fields with the type Offset rather than Color

schultek commented 10 months ago

Strange, I cannot reproduce that. Whats your build_runner version?

point-source commented 10 months ago

Pubspec.yaml:

environment:
  sdk: ">=3.0.6 <4.0.0"

dependencies:
  flutter:
    sdk: flutter
  cupertino_icons: ^1.0.2
  uuid: ^3.0.6
  collection: ^1.17.1
  dart_mappable: ^4.0.1
  flutter_riverpod: ^2.3.6
  riverpod_annotation: ^2.1.1
  fast_immutable_collections: ^9.1.5

dev_dependencies:
  flutter_test:
    sdk: flutter
  flutter_lints: ^2.0.0
  dart_mappable_builder: ^4.0.1
  build_runner: ^2.4.6
  riverpod_generator: ^2.2.3
  custom_lint: ^0.5.3
  riverpod_lint: ^2.1.0

Versions from my pubspec.lock:

build_runner: 2.4.6
build: 2.4.1
collection: 1.17.2
meta: 1.9.1
type_plus: 2.1.0
analyzer: 6.2.0
source_gen: 1.4.0
point-source commented 10 months ago

Strangely, I also have this happening on an older and larger project which is still on dart_mappable 3.1.1. Wondering if maybe it's possible for my sdk to be corrupt? What else could this be?

schultek commented 8 months ago

It is apparent to me that it has something todo with dart:ui being sometimes not resolved by the analyzer, therefore types from it like Color or Offset are not resolved. But I still don't know how to reproduce it.

point-source commented 8 months ago

Happy to set up a meeting / screen share or something if you want to dig through. I will try to get an example repo going as well.

point-source commented 8 months ago

I've managed to make what should be a very minimal reproduction of it. If I follow the steps in the readme, I get invalidtype while generating files with this repo: https://github.com/point-source/mappable_invalid_type

point-source commented 8 months ago

I figured it out. It's the difference between using flutter pub run and dart run to run the build_runner. While this makes sense to me, the reason I was using dart run is because when you use flutter pub run, it outputs this deprecation warning: Deprecated. Use 'dart run' instead.

So either there should be way for analysis to detect Color and Offset, etc while using the dart run command or there is something about dart run that doesn't fully replace flutter pub run and this is either a bug or a breaking change.

Current workaround is to use the deprecated flutter pub run method.

EDIT: Related but did not fix: https://stackoverflow.com/questions/76545047/flutter-pub-run-build-runner-build-is-deprecated

EDIT 2: Turns out this is because I have both dart and flutter installed on my machine because sometimes I write native dart (non-flutter) apps. So while the flutter sdk contained a dart binary, my default dart binary was the standalone (and often more up-to-date) version. This apparently means it cannot identify flutter-specific types. If I use the dart binary included in the flutter sdk, then this works.

Is the flutter team modifying the version of dart that gets packaged with flutter? Does dart 3.2.3 standalone =/= dart 3.2.3 bundled?

I wonder what the "correct" way is to use both flutter and the latest native dart on the same machine. :/

point-source commented 8 months ago

And now, after having had it working successfully for several days, it is suddenly doing it again even though I'm using the correct binary. This is a really pesky bug. It's only occurring on one of my projects now instead of all of them. So I'm thinking this may be a compound issue.

schultek commented 8 months ago

Thanks for all your investigations. This all looks like its something with the dart/flutter setup and less something I "fix" on the package side.

One thing I might be able todo is to catch any "InvalidType" and just use the ast name instead of relying on the analyzer to resolve it. That could potentially circumvent the problem entirely. But no promises as I haven't tested if this actually works.

point-source commented 8 months ago

I agree. The only reason I didn't close this is because of the off-chance that you wanted to try to workaround or warn/alert when this misconfiguration is detected. But ultimately, this isn't a library issue. At least it's documented here now.

As for it returning, this seemed to have something to do with a VSCode cache issue. I have now resolved it again. So I'm back to operational. Thanks for the ongoing support. Feel free to close or leave open if you want to do anything with this. Your call. Thanks

I'm probably going to delete the PoC repo though if that's alright with you.