Flutter How to get the value of a Provider out - flutter

I have this Provider in my code. its suppose to return list
class ServingProvider extends ChangeNotifier {
List<Serving> servings = [];
getServings() {
Firestore.instance
.collection('servings')
.getDocuments()
.then((value) => value.documents)
.then((value) =>
value.map((serving) => Serving.fromJson(serving.data)).toList())
.then((value) => servings.addAll(value))
.then((value) => notifyListeners());
print(servings);
}
// List<Serving> get servings => servings;
}
I want to get the value here but I'm getting null for some reason.
class KMultiScrollWheel extends StatelessWidget {
final String title;
final String value;
final BuildContext passedContext;
final String dialogTitle;
KMultiScrollWheel({
#required this.title,
this.value,
#required this.passedContext,
this.dialogTitle,
});
#override
Widget build(BuildContext context) {
final servings = Provider.of<ServingProvider>(context).servings;
print(servings)
This is the wrapper of my class.
ChangeNotifierProvider(
create: (_) => ServingProvider(),
child: KMultiScrollWheel(
title: 'Serving Size',
passedContext: context,
dialogTitle: 'Select Serving',
),
),
I'm coming from react and I'm completely lost for something that should be rather simple. thanks in advance.
One last question whats the point of using get method when I can access the servings already declared at the top.

As you are using realtime database, use StreamProvider instead of ChangeNotifierProvider , this medium article can help you out

I am new to this and can't comment since I am not sure if this will work, but I don't see you calling the getServings method, so maybe your list isn't filled yet?
You could try something like this:
final servingsProvider = Provider.of<ServingProvider>(context);
(await) servingsProvider.getServings(); //and make getServings() async since it connects to Firebase
print(servingsProvider.servings);
Of course you should handle this in a future or streambuilder, to render it.

I think getServings is a Future function, so you must handle the case when servings is null. You can simply add getServings in the constructor of ServingProvider:
class ServingProvider extends ChangeNotifier {
ServingProvider(){
// call getServings in constructor, notify listener when servings update
getServings();
}
...
and handle null case of servings in KMultiScrollWheel build by your own.
The other ways are FutureProvider (set initialData when data is not ready) or StreamProvider.

Related

Modifying Lists inside of classes - Flutter, Riverpod, Hooks

I would like to have a class that contains a list of another class and be able to update it using Riverpod/Hooks. I'm trying to figure out the best syntax or even way to approach the issue.
I'm starting with a StateProvider to make it simpler and thought to use the List method .add() to add to the list but I'm getting the error: "The method 'add' can't be unconditionally invoked because the receiver can be 'null'." or null check operator used on null value (when I add the !)- so I think I'm not writing it correctly and I'm sure it has to do with the Riverpod or even Dart syntax.
Any suggestions or resources would be appreciated. Thanks in advance.
So for example:
Session({this.title, this.sessionThings})
String? title;
List<SessionThings> sessionThings;
}
class SessionThings{
SessionThings({this.title, this.time})
String? title;
double? time;
}
ref.read(buildExSessionProvider.notifier).state = Session()..sessionThings.add(SessionThings(title: 'something', time: 20);
using:
hooks_riverpod: ^1.0.3
flutter_hooks: ^0.18.3
Do not access state outside a notifier. Something like this could work:
class SessionStateNotifier extends StateNotifier<Session?> {
SessionStateNotifier() : super(null);
void create(String title) => state = Session(title: title);
void add(SessionThings things) =>
state = Session(title: state?.title, sessionThings: [...state?.sessionThings ?? [], things]);
}
Use with hooks
class MyApp extends HookConsumerWidget {
#override
Widget build(BuildContext context, WidgetRef ref) {
final state = useState(0);
final Session? session = ref.watch(sessionProvider);
final SessionStateNotifier sessionStateNotifier = ref.watch(sessionProvider.notifier);
return <widget>
}
}
``

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.

How can I use flutter provider to get data from Firestore?

I used here Future Provider to get data from firestore But it's not allowing me to set the initial Data to null??? It ask me to input a type of . How can I use future Provider to get data from firestore.
class MyHomePage extends StatelessWidget {
final _auth = FirebaseAuth.instance;
#override
Widget build(BuildContext context) {
return FutureProvider<DocumentSnapshot>(create: (_)async{
return FirebaseFirestore.instance.collection("User").doc("xxxx").get();}, initialData: ,child: Welcome,)
}}
Widget Welcome (BuildContext context){
final document = Provider.of<DocumentSnapshot>(context).data;
if(document==null){
return Container(
child: Text("Loading"),);}
}
Instead of creating a FutureProvider of DocumentSnaphot, a good solution would be to create a class that wraps the DocumentSnapshot. For example:
class MyClass {
MyClass(){}
Future<DocumentSnapshot> getData() async {
return await FirebaseFirestore.instance.collection("User").doc("xxxx").get();
}
}
And in the provider declaration you might set something like
...
Provider(create: (_) => MyClass())
...
This wouldn't require you to set the initial data.
However, for your case and what it seems that you are trying to do, using an StreamProvider would be better.
For more examples and details on this, I recommend checking out the following websites. You'll find more useful information there.
https://firebase.flutter.dev/docs/firestore/usage
https://pub.dev/documentation/cloud_firestore/latest/

Possible to provide a "resolved" StreamProvider to child widgets via nested ProviderScope?

I am trying to inject a "resolved" Riverpod StreamProvider object into the tree below to remove some unnecessary async calls. If my interpretation of the docs is correct, a nested ProviderScope should help with this but I am getting a runtime exception.
My use case: I need to access a user-specific specs object high in the widget tree. Some of the data from that object is required all over the remainder of the app, including as a parameter for any DB operation. The specs object comes from firebase and is retrieved async with a StreamProvider.
Once execution is inside the HomePage widget I know the specs object must be loaded and valid so I don't want to fetch it again as a Stream Provider that needs to handle load and error cases. This is especially true where the specs provider is input to other combined providers as the additional load and error cases add lots of unnecessary complexity.
// Called at the root of the tree to retieve some firestore object
final specsStreamProvider = StreamProvider<Specs?>((ref) {
return ref.read(baseDatabaseProvider).currentSpecs();
});
// Called further down to provide the object that was retrieved
final specsProvider = Provider<Specs>((ref) {
throw UnimplementedError('should have been overwritten');
});
// An example of how content will be retrieved from firestore at HomePage widget and below.
// Having to use specsStreamProvider here quickly turns into a mess.
final recordStreamProvider = StreamProvider.autoDispose<List<Record>>((ref) {
final specs = ref.read<Specs>(specsProvider);
final database = ref.read(contentDatabaseProvider(specs.current!));
return database.recordsStream();
});
class SetupWidget extends ConsumerWidget {
const SetupWidget({Key? key, required this.setupBuilder, required this.homeBuilder}) : super(key: key);
final WidgetBuilder setupBuilder;
final WidgetBuilder homeBuilder;
#override
Widget build(BuildContext context, ScopedReader watch) {
final specsAsyncValue = watch(specsStreamProvider);
return specsAsyncValue.when(
data: (specs) => _data(context, specs),
loading: () => const Scaffold(/.../),
error: (e, __) => Scaffold(/.../),
));
}
Widget _data(BuildContext context, Specs? specs) {
if (specs != null) {
return ProviderScope(
// The plan here is to introduce the resolved specs into the tree below
overrides: [specsProvider.overrideWithValue(specs)],
child: homeBuilder(context),
);
}
return setupBuilder(context);
}
}
According to the Riverpod API a nested ProviderScope is a valid tool to overwrite providers for part of the widget tree. Unfortunately, in my case I get a runtime error 'Unsupported operation: Cannot override providers on a non-root ProviderContainer/ProviderScope'
I also tried to make specsProvider a ScopedProvider but then the combined recordStreamProvider doesn't compile. ('error: The argument type 'ScopedProvider' can't be assigned to the parameter type 'RootProvider<Object?, Specs>'.'
I think I figured it out. I made specsProvider a ScopedProvider set in the parent and changed recordStreamProvider (the one that is called in the children only) to not depend on the scoped provider directly.
However, I would still love to hear from one of the Riverpod experts if what I am doing here is acceptable and no anti-pattern.
Parent setting scoped provider:
final specsStreamProvider = StreamProvider<Specs?>((ref) {
return ref.read(baseDatabaseProvider).currentSpecs();
});
// Called further down to provide the object that was retrieved
// This MUST be a ScopedProvider
final specsProvider = ScopedProvider<Specs>((ref) {
throw UnimplementedError('should have been overwritten');
});
class SetupWidget extends ConsumerWidget {/* as before */}
Children consuming scoped provider
// no dependency on specsProvider here
final recordStreamProvider = StreamProvider.family.autoDispose<List<Record>, String>((ref, storeId) {
final database = ref.read(contentDatabaseProvider(storeId));
return database.recordsStream();
});
class HomePage extends ConsumerWidget {
#override
Widget build(BuildContext context, ScopedReader watch) {
final specs = watch(specsProvider);
final recordsAsyncValue = watch(recordsStreamProvider(specs.storeId!));
return recordsAsyncValue.when(
data: (records) => /* build a list */
loading: () => /* show a progress indicator */,
error: (e, __) => /* show an alert dialog */,
));
}
}

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

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);