Home > Enterprise >  Flutter / RiverPod - how to load StateProvider initial state from Firestore
Flutter / RiverPod - how to load StateProvider initial state from Firestore

Time:12-06

I'm stuck with a situation where I am trying to use a RiverPod provider in my Flutter app to represent user preference data. In this case, the user preference data is stored in FireStore.

I'm stuck with understanding how to load provider state from Firestore. Currently, I'm trying to use the "userPreferencesFutureProvider" to load the 'GdUserPreferences" data from a service that calls Firestore, which then pushes the data into "userPreferencesProvider" using the 'overrideWith' method. However, when I access the user preference data via the 'userPreferencesProvider' provider the data loaded from Firestore is not present

final userPreferencesFutureProvider = FutureProvider<bool>( (ref) async {
  final p = ref.watch(userPrefsServiceProvider);
  GdUserPreferences? aPrefs = await p.load();
  if (aPrefs == null) {
    aPrefs =  GdUserPreferencesUtil.createDefault();
  }
  userPreferencesProvider.overrideWith((ref) => UserPreferencesNotifier(p,aPrefs!));
  return true;
});

final userPreferencesProvider = StateNotifierProvider<UserPreferencesNotifier,GdUserPreferences>((ref) {
  return UserPreferencesNotifier(ref.watch(userPrefsServiceProvider),GdUserPreferences());
});

Any suggestions?

Update

Further to the feedback received I have updated my code as shown below, but this still doesn't work...

final userPreferencesFutureProvider = FutureProvider<bool>( (ref) async {
  // get service that wraps calls to Firestore
  final p = ref.watch(userPrefsServiceProvider);
  // load data from Firestore
  GdUserPreferences? aPrefs = await p.load();
  // if none found then create default values
  if (aPrefs == null) {
    aPrefs =  GdUserPreferencesUtil.createDefault();
  }
  // push state into a provider that will be used
  ref.read(userPreferencesProvider.notifier).update(aPrefs);
  // this future returns a boolean as a way of indicating that the data load succeeded.
  // elsewhere in the app access to the user preference data is via the userPreferencesProvider
  return true;
});

final userPreferencesProvider = StateNotifierProvider<UserPreferencesNotifier,GdUserPreferences>((ref) {
  print('default provider');
  return UserPreferencesNotifier(ref.watch(userPrefsServiceProvider),GdUserPreferences());
});

class UserPreferencesNotifier extends StateNotifier<GdUserPreferences> {

  // service is a wrapper around FireStore collection call
  final GdUserPreferencesService service;

  UserPreferencesNotifier(this.service, super.state);

  void update(GdUserPreferences aNewPrefs) {
    print('update state');
    state = aNewPrefs;
  }

}

The purpose of having a separate FutureProvider and StateNotifierProvider, is that I can insert the FutureProvider when the app first loads. Then when I want to access the user preference data I can use the straight forward StateNotifierProvider, which avoids the complications of having Future Providers all over the app.

Note: using the print methods I can show that the 'update' method is called after the userPreferencesProvider is first created, so I can't understand why the data is not updated correctly

CodePudding user response:

It looks like you are trying to use a FutureProvider and a StateNotifierProvider together to load data from Firestore and make it available to your app via a provider.

One potential issue with your current approach is that you are using the overrideWith method to update the userPreferencesProvider with the data from Firestore, but this method is only intended to be used for testing purposes. Instead of using overrideWith, you could try using the read and write methods on the userPreferencesProvider to update its state with the data from Firestore.

Here is an example of how you could use the read and write methods to load data from Firestore and update the userPreferencesProvider with that data:

final userPreferencesFutureProvider = FutureProvider<bool>( (ref) async {
  final p = ref.watch(userPrefsServiceProvider);
  GdUserPreferences? aPrefs = await p.load();
  if (aPrefs == null) {
    aPrefs =  GdUserPreferencesUtil.createDefault();
  }

  // Get the current state of the userPreferencesProvider
  final currentState = ref.watch(userPreferencesProvider.state);

  // Update the state with the data from Firestore
  ref.read(userPreferencesProvider).write(currentState.copyWith(aPrefs!));

  return true;
});

final userPreferencesProvider = StateNotifierProvider<UserPreferencesNotifier,GdUserPreferences>((ref) {
  return UserPreferencesNotifier(ref.watch(userPrefsServiceProvider),GdUserPreferences());
});

Another thing to consider is that you are currently using a FutureProvider to load the data from Firestore, but this provider only returns a bool value. This means that you will not be able to access the actual data that was loaded from Firestore via the userPreferencesFutureProvider. Instead of using a FutureProvider, you could try using a ProxyProvider to load the data from Firestore and make it available to the userPreferencesProvider.

Here is an example of how you could use a ProxyProvider to load the data from Firestore and update the userPreferencesProvider:

final userPreferencesProvider = StateNotifierProvider<UserPreferencesNotifier,GdUserPreferences>((ref) {
  return UserPreferencesNotifier(ref.watch(userPrefsServiceProvider),GdUserPreferences());
});

final userPreferencesProxyProvider = ProxyProvider<UserPrefsService,GdUserPreferences>((ref, service) async {
  GdUserPreferences? aPrefs = await service.load();
  if (aPrefs == null) {
    aPrefs =  GdUserPreferencesUtil.createDefault();
  }
  ref.read(userPreferencesProvider).write(aPrefs!);
  return aPrefs!;
});

I hope this helps!!1

CodePudding user response:

Apologies to all responders...

The problem was caused by a coding error on my side. I had two versions of 'userPreferencesProvider' defined in two different locations. Taking out the rogue duplicate definition and it now works fine.

  • Related