ProviderNotFoundException was thrown - flutter

This is the code I'm using
class Wrapper extends StatelessWidget {
#override
Widget build(BuildContext context) {
final user = Provider.of<User>(context);
if (user == null) {
return LoginPage();
} else {
return MyHomePage();
}
}
}
and the user file has
class User {
final String uid;
User({this.uid});
}
And I am getting this error:
The following ProviderNotFoundException was thrown building Wrapper(dirty):
Error: Could not find the correct Provider above this Wrapper Widget
This likely happens because you used a BuildContext that does not include the provider
of your choice. There are a few common scenarios:
The provider you are trying to read is in a different route.
Providers are "scoped". So if you insert of provider inside a route, then other routes will not be able to access that provider.
You used a BuildContext that is an ancestor of the provider you are trying to read.
Make sure that Wrapper is under your MultiProvider/Provider.
This usually happens when you are creating a provider and trying to read it immediately.

You need to provide the User object somewhere in a widget above Wrapper
Something like this:
Provider(
create: (_) => User(),
child: Wrapper()
)
https://pub.dev/documentation/provider/latest/

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/

Dispose/reset providers after logging out

How can I dispose or reset all my providers?
If user logged out I need to reset all providers, so all provider flows are triggered again for new logged-in user.
How can I achieve this?
Thanks
It is the job of your Provider to handle the disposal: It should close streams when the provider widget is removed from the tree.
What you need to do is to place your Providers above only "logged-in user" widget tree, so that when user is logged out and placed above lets say to login route your providers will be disposed with removed widgets automatically.
The following example instantiates a Model once, and disposes it when
Provider is removed from the tree.
class Model {
void dispose() {}
}
class Stateless extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Provider<Model>(
create: (context) => Model(),
dispose: (context, value) => value.dispose(),
child: ...,
);
}
}
It is worth noting, that Provider.value exposes an existing value without disposing it.

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.

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 */,
));
}
}

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.