Flutter Provider, update StreamProvider when his parameter changes - flutter

I've been developing this Flutter application in which one widget needs to read a list of Objects from firestore database.
To do so, I've a model for the specific Object (which is called Polizza).
I've a class called DatabaseService which contains the methods for getting the Streams.
In this case, this is the method I use to query the DB and get a stream:
Stream<List<Polizza>> streamPolizze({String id, String filter = ''}) {
var ref = _db
.collection('polizze')
.doc(id)
.collection('elenco')
.orderBy('indice');
if (filter.isNotEmpty) {
List<String> list = filter.split(' ').map((e) => e.toUpperCase());
ref = ref.where('indice', arrayContainsAny: list);
}
return ref.limit(10).snapshots().map(
(list) => list.docs.map((doc) => Polizza.fromFirestore(doc)).toList());
}
In the parent widget I was using a StreamProvider to enable his children to access the List of Polizza:
#override
Widget build(BuildContext context) {
var user = Provider.of<User>(context);
var filter = Provider.of<FilterPolizze>(context);
return MultiProvider(
providers: [
StreamProvider(create: (BuildContext context) => DatabaseService().streamPolizze(id: user.uid))
],
child: ...
);
}
It worked.
But now I want to apply a filter to the query.
For doing so I've built a simple TextField widget, which is able to provide me the filter value throught a ChangeNotifierProvider.
This works fine, in fact I can read the filter and his updates from Provider.of(context).
FilterPolizze is the Object type I've built for the filter.
I've tried to update the StreamProvider like that:
StreamProvider(create: (BuildContext context) => DatabaseService().streamPolizze(id: user.uid, filter: filter.value))
But it seems like it doesn't rebuild.
How can I notify the StreamProvider to rebuild itself if one of his parameters change?

Give the SteamProvider a Key which you change when the filter changes.
So for instance use a key with the value 'polizze${filter.value}'

Related

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/

Access Providers from Dialogs for Flutter hooks

I am new to Flutter hooks and riverpod
Basically I have a provider that stores the list of books in a book shelf.
class BookList extends StateNotifier<List<BookModel>> {
BookList() : super([]);
void setBookList(List<BookModel> bookList) =>
{state = bookList};
}
final bookListProvider = StateNotifierProvider<BookList>((_) => BookList());
Then I have a page which display the books and a create button which will shows the create a new book dialog:
class BookShelfPage extends HookWidget {
#override
Widget build(BuildContext context) {
final bookList = useProvider(bookListProvider.state);
useEffect(() {
//API to get list of books
context.read(bookListProvider).setBookList(//data from API);
},[]);
final Function() onCreateBookButtonClicked = () {
showDialog(
context: context,
builder: (context) => ProviderScope(
child: (new BookCreateDialog())));
};
//Data is available for this
print("book list length 1: " + bookList.length.toString());
}
However, I am unable to access the provider values in the dialog:
class BookCreateDialog extends HookWidget {
#override
Widget build(BuildContext context) {
final bookList = useProvider(bookListProvider.state);
//Data is not available for this
print("book list length 2: " + bookList.length.toString());
}
}
Things to note:
I have a ProviderScope wrapping my application.
I have no problems persist or access the providers across different PAGES or any child widget that resides on the PAGES but I am not able to access the provider values from dialogs.
Of course, I can pass the providers' values as parameters to the dialogs but I would like to know if there is any way that I can avoid this as I got a lot values to get from providers.
May I know how to fix this? Much thanks!
You only want to use ProviderScope in two cases. The first is wrapping your app as you mentioned. The other case is when using ScopedProvider.
What you're essentially doing here:
builder: (context) => ProviderScope(child: BookCreateDialog());
is creating a new scope where the value of your StateNotifierProvider is not available (as that value lies within the ProviderScope at the root of your app).
Remove that ProviderScope and you should get the results you are expecting.

Flutter: Update class variable when global variable changes with MVVM

I have my app setup in such a way that a SessionRepository provider wraps the MaterialApp. This repository allows me to track data about the session throughout the app.
#override
Widget build(BuildContext context) {
return Provider<SessionRepository>(
create: (_) => SessionRepository()),
child: MaterialApp(
...
),
);
}
In one of my screens, I create a view model as I'm following MVVM architecture, and use the SessionRepository to initialize some view model variables.
Widget build(BuildContext context) {
final session = Provider.of<SessionRepository>(context, listen: false);
return Provider<TestViewModel>(
create: (context) => TestViewModel(session),
child: ...
);
}
In my view model this is happening:
class TestViewModel{
final SessionRepository session;
final var foo;
final var bar;
TestViewModel(this.session) : foo = session.foo, bar = session.bar;
}
My question is that whenever I update the session variables, I also want the view model to store the updated variables. I can't think of any way that this could be done automatically when updating the session, any inputs?
If you want to notify your view , when SessionRepository property change , and recreate your viewmodel with new values, SessionRepository must implement ChangeNotifier. Check out the ChangeNotifierProvider samples.

error: The argument type 'MaterialPageRoute' can't be assigned to the parameter type 'Route<Map>'

I am learning flutter and this code is straight from the teacher. I know that flutter changing all the time and this may be why it is not running. anyways here it is:
class Klimatic extends StatefulWidget {
#override
_KlimaticState createState() => new _KlimaticState();
}
class _KlimaticState extends State<Klimatic> {
String _cityEntered;
Future _goToNextScreen(BuildContext context) async {
Map results = await Navigator
.of(context)
.push(new MaterialPageRoute<dynamic>(builder: (BuildContext context) {
return new ChangeCity();
}));
if ( results != null && results.containsKey('enter')) {
_cityEntered = results['enter'];
// debugPrint("From First screen" + results['enter'].toString());
}
}
Without knowledge of the exact API details, it looks like you are expecting a type of Route<Map>, but you're creating a Route<dynamic> (or MaterialPageRoute<dynamic>). I'm assuming you could try:
new MaterialPageRoute<Map>(...)
... instead.
This is because the current Flutter reject the parameter type of dynamic for MaterialPageRoute.
You need to change the following code:
Map results = await Navigator
.of(context)
.push(new MaterialPageRoute<dynamic>(builder: (BuildContext context) {
return new ChangeCity();
}));
to
Map results = await Navigator.of(context)
.push(MaterialPageRoute(builder: (BuildContext context) {
return ChangeCity();
})
);
As you can see from the above changes, you don't need the dynamic keyword. And there is no need for new keyword anymore because it's optional now.