Access Providers from Dialogs for Flutter hooks - flutter

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.

Related

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/

Flutter : how to fetch data and manage app state

I'm trying Flutter and I need (I think I do) an app state management to share datas across widgets and dont have to make an http request each time a route is called.
I have Places and Events, so I first load my Places to list them at creation of app state with :
void main() {
runApp(
ChangeNotifierProvider(
create: (context) => AppStateModel()..fetchPlaces(),
...
)
);
}
When I click on a Place, I go on place/id screen and as Places doesnt have events props yet, I'm trying to load them with :
class PlacePageArguments {
final String id;
PlacePageArguments(this.id);
}
class PlacePage extends StatefulWidget {
const PlacePage({Key? key}) : super(key: key);
#override
State<PlacePage> createState() => _PlacePageState();
}
class _PlacePageState extends State<PlacePage> {
String id = '';
#override
Widget build(BuildContext context) {
final args =
ModalRoute.of(context)!.settings.arguments as PlacePageArguments;
return Consumer<AppStateModel>(builder: (context, appState, child) {
id = args.id;
appState.fetchEvents(id);
final place = appState.getPlaceById(id);
return Scaffold(...);
})
}
}
But for sure, as I notifyChange to update widget, It does an infinite loop on fetch events.
What should I do ?
What is the best to achieve something like that, maybe a simple futurBuilder will work, but I want to add events and stay on the same page (add event with modal) and want instant result.
thanks for all
You have two options for bringing in the data for this and none of them require ChangeNotifierProvider.
You can pass data via constructors. This works fine for small widget trees but it can easily get complicated.
You can use Providers. Providers allow you to manage data and functions in one class that stays in one file. For more deals please look here.

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 to create a separate routing for a module in Flutter?

I have two pages: ConversationsListScreen (it displays list of conversations) and ConversationScreen (shows a particular conversation, user gets in here from ConversationsListScreen).
These two pages should be wrapped into a separate module because they both are needed the same data (that I'd like to provide via common cubit class). So that I've created a MessagesModule.
class MessagesModule extends StatelessWidget {
final String _currentUserId;
final String _currentLocale;
const MessagesModule({
#required String currentUserId,
#required String currentLocale,
}) : _currentUserId = currentUserId,
_currentLocale = currentLocale;
#override
Widget build(BuildContext context) {
return BlocProvider<MessagesModuleCubit>(
create: (context) => MessagesModuleCubit(_currentUserId, _currentLocale),
child: ConversationsListScreen(),
);
}
}
After redirection to ConversationScreen it opens on the same level as MessagesModule, it means that my context doesn't contain MessagesModuleCubit. But I'd like to see ConversationScreen nested to MessagesModule, the same as ConversationsListScreen. Here is a current structure of my widgets
How can I manage routing for these two pages so that I can use their common state from MessagesModuleCubit?
You can move the provider to your main file so it wraps both of the widgets in any case

Flutter Provider, update StreamProvider when his parameter changes

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}'