I was following the Resocoder tutorial on how to manage states with RiverPod and StateNotifier.
What kind of trips me is as to how to call the .getWeather on an initial load with some let say default value. The example only illustrates using context.read(..) in the onPressed(..) function which is what is recommended in the riverpod docs.
But then how do you actually make a call on load, since that would mean calling context.read in the build method which is highly discouraged. (mentioned in the last part of this section)
Because .getWeather is a Future function, you can actually add the future initialization inside the constructor of WeatherNotifier and let it update the state itself.
final weatherNotifierProvider = StateNotifierProvider(
(ref) => WeatherNotifier(ref.watch(weatherRepositoryProvider)),
);
class WeatherNotifier extends StateNotifier<WeatherState> {
final WeatherRepository _weatherRepository;
WeatherNotifier(this._weatherRepository) : super(WeatherInitial()){
getWeather('some city name'); // add here
}
Future<void> getWeather(String cityName) async {
try {
state = WeatherLoading();
final weather = await _weatherRepository.fetchWeather(cityName);
state = WeatherLoaded(weather);
} on NetworkException {
state = WeatherError("Couldn't fetch weather. Is the device online?");
}
}
}
Look into .family modifier, you can pass data to a provider returned data/state.
https://riverpod.dev/docs/concepts/modifiers/family
Listening to state with a parameter:
final state = ref.watch(myProvider('my arg'));
Creating a provider:
myProvider = StateNotifierProvider.family<String>((ref, arg) => "Hello $arg");
If you print state you'll get
print(state); //prints "Hello my arg"
Related
Okay I have a FlutterFire app and using Riverpod to manage the state of my app. I have a Listenable Provider named userProvider where I get the user data from Cloud Fierestore. Note that I have a Stream Provider which updates the userProvider. Here's what I mean:
Stream<UserModel> getUserData(String uuid) {
return _users.doc(uuid).snapshots().map(
(event) => UserModel.fromMap(event.data() as Map<String, dynamic>));
}
That above is my Stream Provider and here's what I mean when saying it updates the state of my userProvider:
void getData(WidgetRef ref, User data) async {
userModel = await ref
.watch(authControllerProvider.notifier)
.getUserData(data.uid)
.first;
ref.read(userProvider.notifier).update((state) => userModel);
}
the above function is in my main.dart file.
My userProvider is like this:
final userProvider = StateProvider<UserModel?>((ref) => null);
okay so I made it nullable because before sign in there'll be no user signed in meaning no data to hold.
When I try to use the userProvider I'm getting an error saying null check operator used on a null value but I use a null check operator because I'm certain the user is signed in now and there should be data store to that Provider.
Plaese help me know what is wrong with my code.
Thank you
You need to check first for null before accessing the data in the Provider.
to check null heres the code:
final user = userProvider.state;
if (user != null) {
// use user
} else {
// handle the case where user is null
}
Probably the provider state is reset, although I don't see you have an .autoDispose modifier. The use this way, you can track at what point in time the state of the provider turns into null:
final userProvider = StateProvider<UserModel?>((ref) {
ref.listenSelf((previous, next) {
print('$next => $previous');
});
ref.onDispose(() {
print('userProvider has been disposed');
});
});
I think it's a great way to stop provider nulling :) Debugging is everything.
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
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.
I have StreamProvider whiсh get data from FireStore where i passing on id of document that
i want to retrieve.
This is StreamProvider in root of widgets:
StreamProvider<List<TheExercise>?>(create: (_) => DatabaseService(programId: context.watch<DataChangeNotifier>().getData).exercises, initialData: null, ),
In "context.watch().getData" i pass id that will change depends on user activity, but i can't figure out how to rebuild streamProvider when it's get a new id.
Now stream is building one time when i listen to it.
final _exercises = Provider.of<List<TheExercise>?>(context)?? [];
Here is code in DatabaseService class:
CollectionReference exercisesCollection(){
final CollectionReference _exercisesCollection = FirebaseFirestore.instance.collection('userData').doc(_auth.currentUser!.uid.toString()).collection('programs')
.doc(programId).collection('exercises');
return _exercisesCollection;
}
Stream<List<TheExercise>> get exercises {
return exercisesCollection().snapshots()
.map((listOfExercise));
DataChangeNotifier class code:
class DataChangeNotifier with ChangeNotifier {String _dataIdOfExercise = '';String get getData => _dataIdOfExercise;void changeIdOfExercise(String dataIdOfExercise) {
_dataIdOfExercise = dataIdOfExercise;
notifyListeners();} }
I solved it replase StreamProvider on StreamProvider.value.
As i understood it takes a value every time depens on link in parameter "value".
StreamProvider<List<TheExercise>?>.value(value: DatabaseService(programId: context.watch<DataChangeNotifier>().getData).exercises, initialData: null),
But will appreciate any answers for additional knowledge.
I have been attempting to create streams to Firestore documents using the uid obtained from my auth Provider:
class AuthService {
...
static final provider = StreamProvider.autoDispose((ref) => FirebaseAuth.instance.onAuthStateChanged);
...
}
However, I am struggling to actually create a StreamProvider dependent on the value from the auth Provider.
class User {
...
static final provider = StreamProvider((ref) {
final stream = ref.read(AuthService.provider);
// Returns AsyncValue<Stream<User>> instead of desired AsyncValue<User>
return stream.map((auth) => Service.user.stream(auth.uid));
});
...
}
I also tried using Computed to return the uid or the stream itself but you cannot read a Computed from a Provider (which makes sense in retrospect).
This question is the most relevant on this topic but it is dealing with Provider, not Riverpod.
P.S. Can a Riverpod tag be created?
Edit:
The answer isn't working quite right. The await for loop is only ever triggering once, whereas a listener catches all events.
static final provider = StreamProvider((ref) async* {
final stream = ref.read(AuthService.provider);
print('userProvider init');
stream.listen((auth) {
print('LISTENED: ${auth?.uid}');
});
await for (final auth in stream) {
print('uid: ${auth?.uid}');
yield* Service.user.stream(auth?.uid);
}
});
This code yields the following on login:
userProvider init
LISTENED: <redacted UID>
uid: <redacted UID>
And then on logout:
LISTENED: null
Where I would expect to see uid: null as well, which would update the stream, but upon any more auth events, only the listener is triggered and no events are caught by the await for loop.
Interestingly, using the flutter inspector, the value emitted by the auth provider never changes, either:
AutoDisposeStreamProvider<FirebaseUser>#95f11: AsyncValue<FirebaseUser>.data(value: FirebaseUser(Instance of 'PlatformUser'))
persists through login/logout events, which could explain this behavior, but I am not sure what to do to fix it.
Any ideas? I have been stuck on this for a while and can't correct the issue.
The problem is, your provider doesn't create a Stream<User> but a Stream<Stream<User>>
As part of 0.6.0-dev, you can use ref.watch to easily combine streams:
class User {
...
static final provider = StreamProvider((ref) {
final auth = ref.watch(AuthService.provider);
return Service.user.stream(auth.uid);
});
...
}
I want to preface this by saying I've only been working with Dart/Flutter for a few months, so feel free to correct anything I say and I will update the answer.
I solved this issue after much trial and error and re-reviewing documentation many times. I guess I made a poor assumption that Providers would update when a Provider they depend on changes.
I found that once a StreamProvider returns (or yields), until it is disposed it will always return the same value it originally did, regardless of dependency changes or events coming from a Stream. This is where Computed is useful but doesn't really work well when you desire to return AsyncValue from your provider (how StreamProvider behaves).
Also, additional confusion was caused by the Flutter Inspector not updating the ProviderScope widget correctly. I have to click around and refresh a few times to see updates to Providers or their state. Anyways...
class AuthService {
...
static final provider = StreamProvider.autoDispose((ref) {
final sub = FirebaseAuth.instance.onAuthStateChanged.listen((auth) => ref.read(uidProvider).state = auth?.uid);
ref.onDispose(() => sub.cancel());
return FirebaseAuth.instance.onAuthStateChanged;
});
static final uidProvider = StateProvider<String>((_) => null);
...
}
class User {
...
static final provider = StreamProvider.autoDispose((ref) {
final uid = ref.read(AuthService.uidProvider)?.state;
return Service.user.stream(uid);
});
...
This solution works given that your UI no longer depends on providers (allowing them to be properly disposed) when a user signs out of your app.
I have this method on bloc
fetchProductAttribute(int _prodId) async {
List<ProductAttribute> productAttribute = await _repository.fetchProductAttribute(_prodId).catchError((err) => _fetcher.addError(err));
_fetcher.sink.add(productAttribute);
}
This will call repository then rest api to the backend. Now I want to filter this data.
I will modify this but it's not working... How to do this properly?
fetchProductAttribute(int _prodId) async {
List<ProductAttribute> productAttribute = await _repository.fetchProductAttribute(_prodId).catchError((err) => _fetcher.addError(err));
for(var c in productAttribute){
if(c.variant==true){
_fetcher.sink.add(productAttribute);
}
}
}
All data still coming up...I want only data with variant true to be displayed on screen. How to do this?
I seen one article on how to do filtering on Stream by using StreamSubscription here and here but this is not what I want. I want to filter out from the earlier part of REST.
As one comment said, use the where operator over the list.
fetchProductAttribute(int _prodId) async {
List<ProductAttribute> productAttribute = await _repository.fetchProductAttribute(_prodId).catchError((err) => _fetcher.addError(err));
productAttribute = productAttribute.where((c) => c.variant == true).toList()
_fetcher.sink.add(productAttribute);
}