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 !
@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.userin aProvider: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!
Most helpful comment
For now this is not supported. I will add support for that syntax in the near future.
An alternative is to extract that
state.userin aProvider:This will correctly filter undesired rebuilds.