Search code examples
flutterriverpod

Can't create an AsyncNotifierProvider when notifier needs a depenency injection:


I'm getting this compiler error in the following provider code:

The argument type 'AsyncCatNotifier Function(dynamic)' can't be assigned to the parameter type 'AsyncCatNotifier Function()'.dartargument_type_not_assignable (new) AsyncCatNotifier AsyncCatNotifier({required NetworkService networkService}) package:coolcatsclient/providers/provider_async_cat.dart

class AsyncCatNotifier extends AsyncNotifier<AsyncValue<Cat?>> {
  final NetworkService networkService;

  AsyncCatNotifier({required this.networkService}) : super();

  Future<void> fetchCat(int catId) async {
    try {
      final cat = await networkService.fetchCat(catId);
      state = AsyncData(AsyncData(cat)); // Update state with fetched cat
    } catch (error, stackTrace) {
      state = AsyncError(error, stackTrace); // Handle errors
    }
  }

  @override
  FutureOr<AsyncValue<Cat?>> build() {
    return const AsyncLoading(); // Default initial state while loading
  }
}

final asyncCatProvider = AsyncNotifierProvider.autoDispose<AsyncCatNotifier, AsyncValue<Cat?>>(
  // compiler error happens here
  (ref) => AsyncCatNotifier(networkService: ref.read(networkServiceProvider)), 
);

Solution

  • There are a few things to keep in mind when using AsyncNotifier.

    1. Do not declare a constructor, the dependencies should be injected in the build method. Either by using Ref.watch or from the build parameters (family).
    2. If you want your AsyncNotifier to be auto disposed, extends AutoDisposeAsyncNotifier, there are also FamilyAsyncNotifier and AutoDisposeFamilyAsyncNotifier. Consider using code generation to avoid dealing with the type of Notifier/Provider.
    3. Do not specify the AsyncNotifier's type argument with AsyncValue. The state of an AsyncNotifier is AsyncValue<T> where T is the type argument of the AsyncNotifier. For instance, your AsyncCatNotifier should probably extends AutoDisposeAsyncNotifier<Cat?>.
    4. I would say returning AsyncLoading() inside the AsyncNotifier's build method is undesirable most of the time. Instead, initialise the AsyncNotifier right a way.
    5. If you do not need to mutate the state of the AsyncNotifier, consider converting it to a FutureProvider.

    Below is how I would implement it:

    // A simple provider for other providers to access NetworkService.
    final networkServiceProvider = Provider<NetworkService>((ref) {
      return NetworkService();
    });
    
    // A simple future auto dispose family provider to fetch a Cat.
    final catProvider = AutoDisposeFutureProviderFamily<Cat, int>((ref, catId) async {
      final networkService = ref.watch(networkServiceProvider);
      final cat = await networkService.fetchCat(catId);
      return cat;
    });
    
    // To consume the catProvider.
    final cat = ref.watch(catProvider(1));
    return cat.when(
              data: (cat) => Text('Cat id: ${cat.id}'),
              loading: () => const CircularProgressIndicator(),
              error: (error, stackTrace) => Text('Error: $error'),
            ),
    

    Below is a Dog version with code generation. Notice how it let you

    • Avoid dealing with different Notifier/Provider's type, you do not have to choose between AsyncNotifier/AutoDisposeAsyncNotifier/FamilyAsyncNotifier/AutoDisposeFamilyAsyncNotifier
    • Define multiple family parameters, supports named parameters and default value.
    @riverpod
    NetworkService networkService(NetworkServiceRef ref) {
      return NetworkService();
    }
    
    @riverpod
    FutureOr<Dog> dog(
      DogRef ref, {
      required int dogId,
    }) async {
      final networkService = ref.watch(networkServiceProvider);
      final dog = await networkService.fetchDog(dogId);
      return dog;
    }
    
    final dog = ref.watch(dogProvider(dogId: 1));