How to add multi notifier provider in flutter - flutter

I am working on Food delivery app and I am using Provider as state management architecture. Problem is when i add a second provider to my app it is giving error.
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MultiProvider(
providers: [
ChangeNotifierProvider<GPSViewModel>(create: (_) => GPSViewModel()),
ChangeNotifierProvider<OTPViewModel>(create: (_) => OTPViewModel()),
],
child: GPS(),
),
);
}
Error is
Error: Could not find the correct Provider<OTPViewModel> above this MobileOTP Widget
In MobileOTP i am accessing the provider like this in init state method
#override
void initState() {
super.initState();
Provider.of<OTPViewModel>(context, listen: false).
verifyMobileNumber(widget.phone,verificationCompleted,verificationFailed,codeSent,codeAutoRetrievalTimeout);
}
The Full error trace is like this
Error: Could not find the correct Provider<OTPViewModel> above this MobileOTP Widget
This happens because you used a `BuildContext` that does not include the provider
of your choice. There are a few common scenarios:
- You added a new provider in your `main.dart` and performed a hot-reload.
To fix, perform a hot-restart.
- 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 MobileOTP is under your MultiProvider/Provider<OTPViewModel>.
This usually happens when you are creating a provider and trying to read it immediately.
For example, instead of:
```
Widget build(BuildContext context) {
return Provider<Example>(
create: (_) => Example(),
// Will throw a ProviderNotFoundError, because `context` is associated
// to the widget that is the parent of `Provider<Example>`
child: Text(context.watch<Example>()),
),
}
```
consider using `builder` like so:
```
Widget build(BuildContext context) {
return Provider<Example>(
create: (_) => Example(),
// we use `builder` to obtain a new `BuildContext` that has access to the provider
builder: (context) {
// No longer throws
return Text(context.watch<Example>()),
}
),
}
What i am doing wrong ?

So basically problem was "Provider is based on InheritedWidget. Only child widgets can inherit parent widget's state.". I was trying to access it otherwise, so it was giving me error. I swap the Material App with Multi provider and it fixes the problem.
Code now becomes
#override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
ChangeNotifierProvider<GPSViewModel>(create: (_) => GPSViewModel()),
ChangeNotifierProvider<OTPViewModel>(create: (context) => OTPViewModel()),
],
child: MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: GPS(),
),
);
}
Thats it !!!

Do not ignore the context, use it while you define them, like this:
MultiProvider(
providers: [
ChangeNotifierProvider<GPSViewModel>(create: (ctx) => GPSViewModel()),
ChangeNotifierProvider<OTPViewModel>(create: (ctx) => OTPViewModel()),
],

Related

Using Provider in the same widget in flutter

I have declared MultipleProviders in my widget and i want to use it to change the color of the App by assaining the variable to the ThemeData Primary swatch but it's giving me this error related to provider . and i have use in other widgets and it's working .i think i am getting this error bacause i am using it in the same widget how i can solve it ?
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
var u = Provider.of<prov>(context);
return MultiProvider(
providers: [ChangeNotifierProvider(create: (_)=>prov())],
child: GetMaterialApp(
theme: ThemeData(primarySwatch: u.col),
title: 'Material App',
home: f(),
),
);
}
}
this is the error
Error: Could not find the correct Provider above this MyApp Widget
This happens because you used a BuildContext that does not include the provider
of your choice. There are a few common scenarios:
You added a new provider in your main.dart and performed a hot-reload.
To fix, perform a hot-restart.
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 MyApp is under your MultiProvider/Provider.
This usually happens when you are creating a provider and trying to read it immediately.
For example, instead of:
Widget build(BuildContext context) {
return Provider<Example>(
create: (_) => Example(),
// Will throw a ProviderNotFoundError, because `context` is associated
// to the widget that is the parent of `Provider<Example>`
child: Text(context.watch<Example>()),
),
}
consider using builder like so:
Widget build(BuildContext context) {
return Provider<Example>(
create: (_) => Example(),
// we use `builder` to obtain a new `BuildContext` that has access to the provider
builder: (context) {
// No longer throws
return Text(context.watch<Example>()),
}
),
}
You're getting the error because the context you're using does not have access to the provider.
The solution is like it says in the error message: you can use a builder instead of a child property for your provider. That creates a new context that reads the provider created.
You should change your build method to this.
Widget build(BuildContext context) {
return MultiProvider(
providers: [ChangeNotifierProvider(create: (_)=>prove())],
//From here is where you make the change
builder: (context, child) {
var u = Provider.of<prov>(context);
return GetMaterialApp(
theme: ThemeData(primarySwatch: u.col),
title: 'Material App',
home: f(),
),
);
}

Could not use Provider.of in child widgets

I have my main() like this with MultiProvider wrapped with LocalizedApp for localization:
void main() async {
setupLocator();
var delegate = await LocalizationDelegate.create(
fallbackLocale: 'fa',
supportedLocales: ['fa'],
);
FluroRouter.setupRouter();
WidgetsFlutterBinding.ensureInitialized();
await FlutterDownloader.initialize(debug: true);
runApp(
LocalizedApp(
delegate,
MultiProvider(
providers: [
StreamProvider<ConnectionStatus>(
create: (context) =>
ConnectivityService().connectivityController.stream,
initialData: ConnectionStatus.offline,
),
ChangeNotifierProvider<AppState>(
create: (BuildContext context) => AppState(),
),
],
child: MyApp(),
),
),
);
}
and MyApp class is as follows again wrapped with LocalizationProvider:
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
final appstate = Provider.of<AppState>(context);
var localizationDelegate = LocalizedApp.of(context).delegate;
SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle(
statusBarColor: appstate.statusBarColor,
));
return LocalizationProvider(
state: LocalizationProvider.of(context).state,
child: GestureDetector(
onTap: () {
FocusScopeNode currentFocus = FocusScope.of(context);
if (!currentFocus.hasPrimaryFocus) {
currentFocus.unfocus();
}
},
child: MaterialApp(
localizationsDelegates: [
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
localizationDelegate
],
supportedLocales: localizationDelegate.supportedLocales,
locale: localizationDelegate.currentLocale,
theme: appstate.currentTheme,
initialRoute: 'landing',
onGenerateRoute: FluroRouter.router.generator,
),
),
);
}
}
but even in the initial route which is 'landing' when I try to use a Provider.of<AppState>(context) it throws this error:
Error: Could not find the correct Provider<AppState> above this Landing 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 Landing is under your MultiProvider/Provider<AppState>.
This usually happen when you are creating a provider and trying to read it immediatly.
For example, instead of:
Widget build(BuildContext context) {
return Provider<Example>(
create: (_) => Example(),
// Will throw a ProviderNotFoundError, because `context` is associated
// to the widget that is the parent of `Provider<Example>`
child: Text(context.watch<Example>()),
),
}
consider using `builder` like so:
Widget build(BuildContext context) {
return Provider<Example>(
create: (_) => Example(),
// we use `builder` to obtain a new `BuildContext` that has access to the provider
builer: (context) {
// No longer throws
return Text(context.watch<Example>()),
}
),
}
I don't know what I'm doing wrong here!
also I'm using Fluro v.1.5.1 for navigation.

Could not find the correct Provider<HomeBloc> above this RestoreLocalBackupPage Widget, how to solve this issue in a simpler manner than what I did?

I'm trying to build a Notes app with backup and restore functionality. I have a home page that shows up when the app is opened. This page has a Scaffold as it's body, which in turn has a drawer that has ListTiles for backup and restore. I use a HomeBloc object to interact with the database where I save the notes, hence I used Provider to get access to it everywhere.
The ListTiles open a MaterialPageRoute to new screens where the user is prompted to choose the file, enter passwords etc.
When I tap on the Restore ListTile in the drawer, I get this error:
The following ProviderNotFoundException was thrown building RestoreLocalBackupPage(dirty, state: _RestoreLocalBackupPageState#4f937):
Error: Could not find the correct Provider<HomeBloc> above this RestoreLocalBackupPage Widget
This likely happens because you used a `BuildContext` that does not include the provider
of your choice.
This is my main.dart, where I wrap the Home page in a Provider:
void main() {
runApp(
MyApp()
);
}
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Notes',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: Provider(
create: (_) => HomeBloc(),
child: HomePage(),
)
);
}
}
This is the build method of my HomePage:
Widget build(BuildContext context) {
homeBloc = Provider.of<HomeBloc>(context);
return Scaffold(
backgroundColor: Color.fromRGBO(219, 243, 250, 1),
appBar: AppBar(...),
body: StreamBuilder<List<Note>>(...),
floatingActionButton: FloatingActionButton(...),
drawer: HomeDrawer(),
);
}
The HomeDrawer's build method returns a Drawer, which has a ListView as it's child. Here's the code for the ListTile that launches the Restore Backup page:
ListTile(
title: Text('Local Backup',
style: GoogleFonts.sourceSansPro(
textStyle: TextStyle(fontWeight: FontWeight.w500),
fontSize: 16)),
onTap: () async {
// Update the state of the app
// ...
// Then close the drawer
bool permissionGranted = await _getPermissions(context);
if (permissionGranted) {
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => CreateLocalBackupPage(
currentBackupDirectory: currentBackupDirectory
),
)
);
}
},
)
This is the error that I get when I tap on the above ListTile:
The following ProviderNotFoundException was thrown building RestoreLocalBackupPage(dirty, state: _RestoreLocalBackupPageState#4f937):
Error: Could not find the correct Provider<HomeBloc> above this RestoreLocalBackupPage Widget
This likely happens because you used a `BuildContext` that does not include the provider
of your choice.
HomeDrawer()'s BuildContext does have access to the HomeBloc object I need. Hence, wrapping the RestoreLocalBackupPage widget inside another Provider works:
HomeBloc homebloc = Provider.of<HomeBloc>(context);
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => Provider(
create: (_) => homebloc,
child: RestoreLocalBackupPage(currentBackupDirectory: currentBackupDirectory),
)
)
);
I wanted to know if there's a simpler, more elegant way of getting access to HomeBloc inside RestoreLocalBackupPage using Provider. Dependency Injection via the constructor works but that sort of defeats the purpose of using Provider in the first place, right?
Wrapping the MaterialApp in main.dart with a Provider solved my issue. I have found the solution here. Check rrousselGit's answer.
After doing that, main.dart now becomes:
void main() {
runApp(
MyApp()
);
}
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
LicenseRegistry.addLicense(() async* {
final license = await rootBundle.loadString('google_fonts/Cabin_OFL.txt');
yield LicenseEntryWithLineBreaks(['google_fonts_Cabin'], license);
});
LicenseRegistry.addLicense(() async* {
final license = await rootBundle.loadString('google_fonts/SSP_OFL.txt');
yield LicenseEntryWithLineBreaks(['google_fonts_SSP'], license);
});
return Provider(
create: (_) => HomeBloc(),
child: MaterialApp(
title: 'Notes',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: HomePage(),
),
);
}
}
Try to use provider one level up and wrap MaterialApp with Provider.

how to dispose the provider that is wrapping the entire MaterialApp in Flutter

When I wrap the widget that is the home of the MaterialApp with MultiProvider
it works fine but when I want to navigate to another page that already contains widgets that depend on the provider,
a message shows up that tell me
"Could not find the correct Provider above .... et cetera"
but when I wrap the entire MaterialApp, It works fine
but the problem is
even when I remove all Widget tree and insert a new page
the provider still has its data and I need it to dispose
cause I can access the data of the provider from inside the newly inserted page after removing all the previous pages from the navigator stack
how can I force the disposing of the provider that is already wrapping the MaterialApp
here is the sample code
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
ChangeNotifierProvider<CommonWidgetsStateProvider>(
create: (context) => CommonWidgetsStateProvider(),
),
ChangeNotifierProvider<CollegePostSignUpState>(
create: (context) => CollegePostSignUpState()),
ChangeNotifierProvider<SchoolStudentPostSignupState>(
create: (context) => SchoolStudentPostSignupState()),
ChangeNotifierProvider(create: (context) => ExecutionState())
],
child: MaterialApp(
debugShowCheckedModeBanner: false,
home: LandingPage(),
routes: NavigatorServices.navigatorRoutes),
);
}
}```

How to create a BlocListener that can listen to all pages in flutter with access to MaterialApp context?

I'm trying to create a BlocListener that has the ability to listen to all pages/routes throughout the app just like how you can access a Bloc or a Provider all throughout the app if they are defined at root-level like in the code below
runApp(
MultiProvider(
providers: [
ChangeNotifierProvider<IdentityTokenProvider>(
create: (_) => IdentityTokenProvider(),
),
],
child: MultiBlocProvider(
providers: [
BlocProvider<AuthBloc>(
create: (_) => AuthBloc(),
),
],
child: MaterialApp(
debugShowCheckedModeBanner: AppConfig.DEBUGGABLE,
theme: ThemeData(
// fontFamily: CustomFontStyle.montserrat,
),
home: AuthListener(
child: Center(
child: const MainApp(),
),
),
),
),
),
);
As you can see, I have providers, blocs, and one listener. I have no problem accessing the blocs and providers in other pages. My problem is the auth listener. I lose access to the AuthListener once I move to a different page (by removing stack) , because it is inside the MaterialApp. However, in this instance, I need that specific listener (AuthListener) to be inside a MaterialApp, because it consists of code that uses page navigations (which doesn't work if the implementation is done outside/above the widget tree of a MaterialApp), and makes us of the MaterialApp context for showing dialogs.
My implementation of page routing which removes the stack, which is another cause of losing access to the AuthListener
Navigator.pushAndRemoveUntil(
context,
MaterialPageRoute(builder: (_) => route),
(Route<dynamic> route) => false);
Why do I remove the route/page stack when moving to a different page?
I specifically use this after authentication. You don't really want a user to be able to press back button after logging in, andredirect the user back to the login page right? Usually back button should hide/close the app when they are logged in.
My AuthListener implementation
class AuthListener extends StatefulWidget {
final Widget child;
const AuthListener({Key key, #required this.child}) : super(key: key);
#override
_AuthListenerState createState() => _AuthListenerState();
}
class _AuthListenerState extends State<AuthListener> {
#override
Widget build(BuildContext context) {
return BlocListener<AuthBloc, AuthState>(
listener: (context, state) {
if (state is AuthAuthenticated) {
PageRouterController.pushAndRemoveStack(context, const EcomPage());
} else if (state is AuthUnauthenticated) {
PageRouterController.pushAndRemoveStack(context, const LoginPage());
}
},
child: widget.child,
);
}
}
Is there a different way around this?
So I ended up defining a
static final GlobalKey<NavigatorState> navigatorKey = new GlobalKey();
and used it in my MaterialApp
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: App.DEBUGGABLE,
theme: ThemeData(
// fontFamily: CustomFontStyle.montserrat,
),
navigatorKey: App.navigatorKey,
home: Center(
child: const LoginPage(),
),
);
}
So then, whenever I have to navigate in cases where the implementation is outside the MaterialApp (in my case via the AuthListener which is found at root-level, above the MaterialApp), I can navigate via
App.navigatorKey.currentState.pushAndRemoveUntil(
MaterialPageRoute(builder: (_) => route),
(Route<dynamic> route) => false);
Which means I can finally have access to the MaterialApp navigator and context even with the listener outside the MaterialApp which allows me to do both navigation and showing of dialogs