2

I have a Flutter class that uses Freezed to create a sealed union that represents either data or an error:

@freezed
class DataOrError<T, E> with _$DataOrError {
  const factory DataOrError.loading() = Loading;

  const factory DataOrError.data(T data) = DataOrE<T, E>;

  const factory DataOrError.error(E error) = DOrError<T, E>;

  static DataOrError<T, E> fromEither<T, E>(Either<E, T> val) {
    final result = val.fold(
        (l) => DataOrError<T, E>.error(l), (r) => DataOrError<T, E>.data(r));
    return result;
  }
}

I use riverpod so I have a riverpod StateNotifier that looks like:

class RolesNotifier
    extends StateNotifier<DataOrError<List<Role>, RoleFailure>> {
  final Ref _ref;
  StreamSubscription? sub;

  RolesNotifier(Ref ref)
      : _ref = ref,
        super(const DataOrError.loading());

  /// Will run the fetch
  void fetch() {
        // fetch roles
        state = const DataOrError.loading();
        sub = _ref.read(firebaseRoleService).getRoles().listen((event) {
          state = DataOrError.fromEither<List<Role>, RoleFailure>(event);
        });
  }

// ... this class has been shortened for simplicity.
}

final rolesProvider = StateNotifierProvider.autoDispose<RolesNotifier,
    DataOrError<List<Role>, RoleFailure>>((ref) {
  return RolesNotifier(ref);
});

When I consume this provider; however, the types for DataOrError are gone:

ref
  .read(rolesProvider)
  .when(loading: (){}, data: (d) {
  // d is dynamic type not List<Role>
        
  }, error: (e){});

For some reason both d and e are dynamic types and not List<Role> & RoleFailure respectively. Everything appears to be typed correctly so why is this not working? I'm not sure if the error is with Freezed or Riverpod. I would like to avoid type casting (i.e. d as List<Role>) because that defeats the purpose of the generics.

Saed Nabil
  • 6,705
  • 1
  • 14
  • 36
Gabe
  • 5,643
  • 3
  • 26
  • 54

2 Answers2

4

The mixin has to be defined with the same generic types Like

class DataOrError<T, E> with _$DataOrError<T,E>

Although if you did not explicitly define the mixin generics ,the runner will mimic the original class types but in that case there will be no relation between them. in our case it will be a different set of T and E that is why it will be dynamic , bottom line is that you have to tell the mixin that it is indeed the same set of types.

enter image description here

Saed Nabil
  • 6,705
  • 1
  • 14
  • 36
  • Hi, I thought this would work but unfortunately after editing the mixin and running the buildrunner, it still doesn't work. – Gabe Jun 08 '22 at 12:43
  • @Gabe attached a photo.. it is working for me ! ,as you can see data is List and not dynamic anymore .check that the build runner is completed with success – Saed Nabil Jun 08 '22 at 13:16
  • Weird, I’ll look at it again tonight, if it works I’ll refresh the bounty for you – Gabe Jun 09 '22 at 14:07
  • @Gabe I added more explanation of why it was not working before ,Good luck this time :) – Saed Nabil Jun 09 '22 at 18:12
  • @Gabe it is working even on dartpad man :) check the dartpad snippet here https://dartpad.dev/?id=4c845e36f112570d63462c9e2e03a593 – Saed Nabil Jun 10 '22 at 04:28
  • Got it working!! Thanks, I have to wait 23 hrs and then you'll have your bounty. – Gabe Jun 10 '22 at 22:02
  • That is before of after you accept the answer ? not sure about bounty rules ,anyways glad that you got it working .thanks – Saed Nabil Jun 11 '22 at 03:03
  • I wish this was called out more explicitly in the documentation! – anqit Oct 31 '22 at 01:34
0

Your code seems to be setup correctly, my intuition is that the types are not being inferred because you are using ref.read. This may be intentional in riverpod for some reason.

Finally, using ref.read inside the build method is an anti-pattern and your UI will not change whenever the state notifier updates.

Replace ref.read(rolesProvider) with:

ref.watch(rolesProvider).when(
  loading: (){},
  data: (d) {}, // Types should be inferred correctly now
  error: (e){},
);
  • Thanks for responding. If it is an issue with riverpod then how does it work for riverpods AsyncValue? And the ref.read a was just an example to demonstrate the typing problem… – Gabe Jun 03 '22 at 17:16