River_pod: Listen to specific value from StateNotifierProvider inside other provider

Created on 25 Sep 2020  Â·  11Comments  Â·  Source: rrousselGit/river_pod

I created a model of the app's state using freezed. This would include chats, user and other global info.

@freezed
abstract class AppState with _$AppState {
  const factory AppState({
    @nullable @required User user,
    @nullable @required AuthDetails authDetails,
    @nullable @required Chat chat,
    // ...
  }) = _AppState;
}

Then I made an AppNotifier that actually held the state of the app and managed it. I exposed it via a simple StateNotifierProvider.

class AppNotifier extends StateNotifier<AppState> {
  AppNotifier(this.ref) : super(AppState.empty());

  final ProviderReference ref;

  User get user => state.user;
  AuthDetails get authDetails => state.authDetails;
  Chat get chat => state.chat;

  Future<User> signIn() async {
    // ...
    final apiService = ref.read(apiProvider);
    final user = apiService.signIn();

    state = state.copyWith(user: user);

    return user;
  }
}

final appProvider = StateNotifierProvider<AppNotifier>((ref) {
  return AppNotifier(ref);
});

But now I want to create a userProvider that is a FutureProvider that will listen to ONLY the user on the StateNotifier and update when it changes. The reason it is a FutureProvider is that if it sees that the App's user is null, it will call signIn and return that user or null if they really can't sign in.

final userProvider = FutureProvider.autoDispose<User>((ref) async {
  final app = ref.watch(appProvider.state);

  if (app.user != null) {
    return Future.value(app.user);
  }

  try {
    final user = await ref.watch(appProvider).signIn();

    return user;
  } catch (_) {
    return Future.value(null);
  }
});

The problem is that there is no way to listen to a specific value on a StateNotifier and if any value gets changed (such as the chat in my example) userProvider will get called again forcing another loading state which is undesirable.

Is there a way to accomplish? To listen to individual values on a StateNotifier inside another provider? Something akin to

final userProvider = FutureProvider.autoDispose<User>((ref) async {
  final user = ref.watch(appProvider.state.select((state) => state.user));
  // ...
});

Thank you !

enhancement

Most helpful comment

final user = ref.watch(appProvider.state.select((state) => state.user));

For now this is not supported. I will add support for that syntax in the near future.

An alternative is to extract that state.user in a Provider:

final _user = Provider((ref) {
   return ref.watch(appProvider.state).user;
});

final userProvider = FutureProvider.autoDispose<User>((ref) async {
  final user = ref.watch(_user);
  // ...
});

This will correctly filter undesired rebuilds.

All 11 comments

@Nolence Have you tried AsyncValue?
dart class​ ​AppNotifier​ ​extends​ ​StateNotifier​<AsyncValue<​AppState​>>​ {   ​AppNotifier​(​) ​:​ ​super​(const Asyncvalue.loading()); //... }
Then you can use whenData().

I just checked it out with my AppNotifier but then that creates a host of other questions in how to manage state. For example, how would I go about actually making changes from inside the AppNotifier to the state? See the signIn method on AppNotifier that sets the value of state to include user.

I don't want to go too far in alternative solutions though before I understand if/why my current implementation is incorrect

final user = ref.watch(appProvider.state.select((state) => state.user));

For now this is not supported. I will add support for that syntax in the near future.

An alternative is to extract that state.user in a Provider:

final _user = Provider((ref) {
   return ref.watch(appProvider.state).user;
});

final userProvider = FutureProvider.autoDispose<User>((ref) async {
  final user = ref.watch(_user);
  // ...
});

This will correctly filter undesired rebuilds.

Thanks Remi !

That alternative works perfectly in the meantime

For now this is not supported. I will add support for that syntax in the near future.

🎉 🎉 🎉

I'm looking forward to select support.

final user = ref.watch(appProvider.state.select((state) => state.user));

I believe this will also answer the question I asked here.

final user = ref.watch(appProvider.state.select((state) => state.user));

For now this is not supported. I will add support for that syntax in the near future.

An alternative is to extract that state.user in a Provider:

final _user = Provider((ref) {
   return ref.watch(appProvider.state).user;
});

final userProvider = FutureProvider.autoDispose<User>((ref) async {
  final user = ref.watch(_user);
  // ...
});

This will correctly filter undesired rebuilds.

It seems that those extra providers are not needed, as I show here.

They are.

The purpose of _user is that if anything _other_ then the user property updates, things that depends on the _user provider will not update

Thank you very much @rrousselGit for the answer! What I understand is the following. If we had:

final _user = Provider((ref) {
   return ref.watch(appProvider.state).user;
});

final userProvider = Provider<User>((ref) {
  return ref.watch(_user);
});

then we could say that the userProvider is useless. The same would be with the following code:

final _buttonState = Provider<ButtonState>((ref) {
  return ref.watch(timerProvider.state).buttonState;
});

final buttonProvider = Provider<ButtonState>((ref) {
  return ref.watch(_buttonState);
}); 

The difference is that here inside the userProvider we are using the _user to do something more than simply returning it, while in buttonProvider we are simply returning _buttonState, so that buttonProvider actually becomes useless (and the result using the buttonProvider will be the same as using _buttonState).

Did I get it right?

Thank you!

Yes

On the other hand the private providers that you mentioned are useful and do reduce rebuilds

Thank you very much!

Was this page helpful?
0 / 5 - 0 ratings