6

I've writen some code that provides a ApiService to a StateNotifier. The ApiService has a dependency on a authenticatorclient - The auth client has to be created asynchronously as it uses sharedprefs to get a token.

Im just trying to figure out if theirs a more elegant way to how I've written this. Basically when the service apiService is injected into the StateNotifier it could be nullable... That to me is a bit of a code smell.

So in brief this is what im doing... use a FutureProvider to Instantiate the RestClientwith a Dio

authenticatorClient = FutureProvider<RestClient>((ref) async {
  final prefs = await SharedPreferences.getInstance();
  final dio = Dio();
  ...
  return RestClient(dio);
}

And then I watch that and use a MaybeWhen to return the service

final clientCreatorWatchProvider = Provider<ApiService?>((ref) => ref
    .watch(authenticatorClient)
    .whenData((value) => ApiService(value))
    .maybeWhen(
      data: (service) => service,
      orElse: () => null,
    ));

So the bit I dont like is the orElse returning null

And then my StateNotifier is watching...

final AppState = StateNotifierProvider<AppNotifier, String>(
    (ref) => AppNotifier(ref.watch(clientCreatorWatchProvider)));

class AppNotifier extends StateNotifier<String> {
  final ApiService? apiService;

  AppNotifier(this.apiService) : super("loading") {
    init();
  }
...
}

Any thoughts on the above approach?

Thanks

aidanmack
  • 518
  • 1
  • 5
  • 16

1 Answers1

17

One way to solve this problem is to initialize SharedPreferences outside of a provider. You can then use ProviderScope to override a synchronous provider, eliminating the need to work with AsyncValue.

When you initialize your app, do the following:

final sharedPreferences = Provider<SharedPreferences>((_) => throw UnimplementedError());

Future<void> main() async {
  final sharedPrefs = await SharedPreferences.getInstance();

  runApp(
    ProviderScope(
      overrides: [
        sharedPreferences.overrideWithValue(sharedPrefs),
      ],
      child: MyApp(),
    ),
  );
}

Now you could write your providers like so:

final authenticatorClient = Provider<RestClient>((ref) {
  final prefs = ref.watch(sharedPreferences);
  final dio = Dio();
  ...
  return RestClient(dio);
}

final clientCreatorWatchProvider = Provider<ApiService>((ref) {
  final authClient = ref.watch(authenticatorClient);
  return ApiService(authClient);
});
Alex Hartford
  • 5,110
  • 2
  • 19
  • 36
  • Does this really work? See https://github.com/rrousselGit/river_pod/issues/575 about providers watching other providers and not getting the overridden value. – wujek Jul 02 '21 at 22:04
  • @wujek For this case, yes it does. I avoid using ProviderScope outside of the root app, like in this example, so I can't speak to other cases. – Alex Hartford Jul 02 '21 at 23:29
  • This is a better solution than most of the answers on SO. Wish I could +1 it more. – DrkStr Apr 04 '22 at 23:36