How do I go about synchronously initialising my provider? (Riverpod) - flutter

I have a provider like so:
final profileImagesProvider =
ChangeNotifierProvider.family<ProfileImagesNotifier, List<String>>(
(ref, profileImages) => ProfileImagesNotifier(profileImages));
class ProfileImagesNotifier extends ChangeNotifier {
ProfileImagesNotifier(List<String> images);
List<String> images;
}
However, when I try to use the provider:
Widget build(BuildContext context, ScopedReader watch) {
var profileImages = watch(ProfileImagesNotifier(['test_1.png', 'test_2.png']))
print(profileImages.images) //null
}
The list is retrieved as a null.
Am I doing this right? (I'm a completely noob when it comes to river pod and state management).

Was incorrectly calling the constructor.
Should have been:
ProfileImagesNotifier(this.images);
rather than
ProfileImagesNotifier(List<String> images);

Related

Flutter Riverpod call StateProvider within a class that itself is a StateProvider

I have a Class that is being called by Widgets but this Class needs to pull data from another Class. Basically, I am using Riverpod as Dependency Injection, and am unsure if this is "correct" or am I doing it wrong. Here is what I did:
main.dart
var myClass1 = Class1();
final class1Provider = StateProvider((ref) => myClass1);
final class2Provider = StateProvider((ref) => Class2(myClass1));
Is this the recommended way or should I do something else?
FYI this does work;
Widget build(BuildContext context) {
displayData = (ref.watch(class2Provider.notifier).state).getData();
thanks
This is incorrect. You are using riverpod but are mixing it with global variables which trashes the whole point of using a state-management library.
You should create instances inside the StateProvider:
final class1Provider = StateProvider((ref) => Class1());
In order to access the value of one provider in another you need to use ref.watch() method inside the body of the provider:
final class2Provider = StateProvider((ref) {
final myClass1 = ref.watch(class1Provider);
return Class2(myClass1);
});
Finally, to consume a StateProvider you are watching the notifier instead of the state. This will give you an initial value correctly but will not rebuild your UI when the state changes. Instead you should watch the provider directly.
Widget build(BuildContext context) {
final displayData = ref.watch(class2Provider).getData();
}
For more info read the docs thoroughly https://riverpod.dev/docs/getting_started/.

What is the efficient way to pass arguments to a Riverpod provider each time it gets initialized in Flutter?

I am currently trying to create an instance of a widget's state (ChangeNotifier) using a global auto-disposable ChangeNotifierProvider. The notifier instance takes in a few arguments to initialize each time the UI is built from scratch.
Let's assume we have the following simple state (or notifier):
class SomeState extends ChangeNotifier {
int _someValue;
SomeState({required int initialValue})
: _someValue = initialValue;
int get someValue => _someValue;
set someValue(int someValue) {
_someValue = someValue;
notifyListeners();
}
}
I used to use the Provider package before switching to Riverpod, where this could've easily been done like so:
class SomeWidget extends StatelessWidget {
#override
Widget build(BuildContext context) {
return ChangeNotifierProvider(
// Passing 2 into state initializer, which may be
// obtained from a different state, but not necessarily.
create: (_) => SomeState(initialValue: 2),
builder: (context, child) => Consumer<SomeState>(
builder: (context, state, child) {
// Will print 2, as it's currently the default value.
return Text('${state.someValue}');
},
),
);
}
}
So with Provider, you can manually call to SomeState constructor with arbitrary arguments when the state is being set up (i.e. provided). However, with Riverpod, it doesn't seem as intuitive to me, mainly because the provider is made to be declared globally:
static final someProvider = ChangeNotifierProvider.autoDispose((ref) => SomeState(2));
Which would end up being used like so:
class SomeWidget extends ConsumerWidget {
#override
Widget build(BuildContext context, WidgetRef ref) {
final state = ref.watch(someProvider);
return Text('${state.someValue}');
}
}
However, with this approach I can't pass parameters like I did in the example using Provider. I also don't want to use the family modifier because I would need to pass the same parameter each time I read/watch the state, even if it's already created.
If it helps, in my current situation I am trying to pass a function (say String Function()? func) into my state on initialization. It's also not feasible to depend on a different provider in this case which would provide such function.
How could I replicate the same functionality in the Provider example, but with Riverpod?
P.S. Apologies if code has syntax errors, as I hand-typed this and don't have an editor with me at the moment. Also, this is my first post so apologies for lack of clarity or format.
Use provider overrides with the param that you need:
First, let's ensure the ProviderScope in the root of the widget-tree.
// Root
ProviderScope(
child: MaterialApp(...)
)
After, create another one in some widget:
Widget build(BuildContext context) {
return ProviderScope(
overrides: [
someProvider.overrideWithProvider(
ChangeNotifierProvider.autoDispose((ref) => SomeState(5)),
),
],
child: Consumer(
builder: (context, ref, child) {
final notifier = ref.watch(someProvider);
final value = notifier.someValue;
return Text('$value'); // shows 5 instead of 2
}
),
);
}
If you do not want to use family then you can put value in another way by combining two providers.
final someValue = StateProvider((ref) => 0);
final someProvider = ChangeNotifierProvider.autoDispose((ref) {
final value = ref.watch(someValue);
return SomeState(value);
});
class SomeState extends ChangeNotifier {
int _someValue;
SomeState(int initialValue) : _someValue = initialValue;
int get someValue => _someValue;
set someValue(int someValue) {
_someValue = someValue;
notifyListeners();
}
}
USAGE:
// From everywhere you can put new value to your ChangeNotifier.
ref.read(someValue.notifier).state++;
But in your case, it's better to use the `family method. It's cleaner and less complicated.

Flutter Riverpod listen not being invoked

I'm trying to implement just a basic listener in a widget (I will want to show a snackbar) but it just isnt being invoked by the provider. Cant see what Im doing wrong here.
I've tried from other widgets and the listener still doesn't hear the event.
Any ideas?
int foo = 1;
final FooProvider = Provider<int>((ref) {
foo = foo + 1;
return foo;
});
class showSnack extends ConsumerWidget {
final int taskID;
const showSnack(this.taskID);
#override
Widget build(BuildContext context, WidgetRef ref) {
ref.listen<int>(FooProvider, (int? previousCount, int newCount) {
logger.d("Fooo event");
});
return TaskInfo(taskID);
}
}
The basic Provider is not a state-holding type of provider. It's basically a static provider of some sort of data or a service class, meaning that it can't be used to watch for state changes or for listening.
You should probably use the StateProvider, StateNotifierProvider or the ChangeNotifierProvider. You can read more about the different providers in the documentation.

Riverpod - fetch data in ConsumerWidget when accessing it

I have a StateNotifier whose state I want to use in a widget.
As far as I know, if you watch a StateNotifierProvider with ref.watch in a build, the widget gets rebuilt every time the state changes.
Now, In the StateNotifier I have a DatabaseService instance to call api requests and set the state accordingly and in a ConsumerWidget I watch the state.
The thing is, I want to call a fetch method defined in StateNotifier the first time the widget builds, so I can display the data retrieved from the database.
Something like this
class MyStateNotifier extends StateNotifier<CustomState> {
final DatabaseService databaseService;
MyStateNotifier(this.databaseService) : super(CustomStateInit());
Future<void> fetchData() async {
state = CustomStateLoading();
final result = await databaseService.apiRequest();
state = CustomStateSuccess(result);
}
}
final stateNotifierProvider = StateNotifierProvider((ref) => MyStateNotifier());
and in the widget
class MyWidget extends ConsumerWidget {
// I have to call the MyStateNotifier fetchData() method to get the data
Widget build(BuildContext context, WidgetRef ref) {
final data = ref.watch(stateNotifierProvider);
return Column(
children: data.map((e) => Text(e)).toList()
);
}
}
To call fetchData() once you watch your stateNotifierProvider you need to call it in the StateNotifier's constructor like this:
MyStateNotifier(this.databaseService) : super(CustomStateInit()){fetchData();}

BuildContext in a Flutter BLoC class

In short, does the context belong in the BLoC class and if it doesn't, what's the right approach?
I'm using a Provider as an abstraction layer between the Firebase DB and the UI. Recently, we've been abstracting further away to use the BLoC pattern, so that the widgets don't manipulate the data in the Provider directly. It's all proceeding nicely, but due to the fact that we use both the providers and the BLoC, I am not sure how to use the BuildContext properly, as the context ha more to do with the Widgets/UI than the business logic.
Here's an example:
class SomeWidget extends StatelessWidget {
final SomeWidgetBloc bloc;
SomeWidget({Key key, this.bloc});
#override
Widget build(BuildContext context) => StreamBuilder(stream: bloc.getSomeData,
builder: (context, snapshot) {
return Text(snapshot.data ?? "Empty");
}
}
class SomeWidgetBloc {
BuildContext context; // should it be here? Currently, it's needed for the Provider
SomeWidgetBloc(BuildContext context);
Stream<String> get getSomeData {
return Provider.of<SomeFirebaseProvider>(context).fetchSomeData();
}
}
You should pass your SomeFirebaseProvider instance to the SomeWidgetBloc constructor at creation time.