ChangeNotifierProvider isn't listening - flutter

I am attempting to share a ChangeNotifierProvider to my main.dart, however the value never gets updated.
How it works
main.dart uses ChangeNotifierProvider to get an instance of the class Location()
main.dart routes to the location_login.dart page where a string in Location() class is set.
The instance of Location() should update in main.dart but it DOES NOT.
Here is the main.dart
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
runApp(
ChangeNotifierProvider<Location>.value( <------ CREATE CHANGENOTIFIERPROVIDER
value: Location(),
child: MyApp(),
),
);
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
Location location = Provider.of<Location>(context, listen: false); <----- LISTEN TO PROVIDER
return MultiProvider(
providers: [
FutureProvider<List<Report>>(
create: (context) =>
Collection<Report>(path: '${location.getLocation}/data/reports') <----- USE PROVIDER STRING IN PATH
.getUsers(),
initialData: [],
),
],
child: MaterialApp(
routes: {
'/': (context) => LocationLogin(),
'/login': (context) => LoginScreen(),
'/home': (context) => HomeScreen(),
},
// Theme
theme: ThemeData(
fontFamily: 'Nunito',
bottomAppBarTheme: BottomAppBarTheme(
color: Colors.black87,
),
// your customizations here
brightness: Brightness.dark,
buttonTheme: ButtonThemeData(),
),
),
);
}
}
Here is the location_login.dart
#override
Widget build(BuildContext context) {
Location location = Provider.of<Location>(context, listen: true);
return Scaffold(
body: TextButton(
child: Text("Submit",
style: GoogleFonts.poppins(
fontSize: 15.sp, color: Colors.white)),
onPressed: () {
location.setLocation('London'); <------- SETTING LOCATION
}),
);
}
}
Here is the location.dart
class Location with ChangeNotifier {
String place = 'none';
String get getLocation => place;
setLocation(String location) {
place = location;
notifyListeners();
}
}
To reiterate, the issue is that when I click the button in the location_login.dart page to set the location to "London"; it does not update the ChangeNotifierProvider with a new instance of the Location() class containing "London". Therefore, I can not update the path in my FurtureProvider. Any ideas of what is going wrong here? I tried to make this as clear as possible but if you don't understand please ask. Thank you

I think you have not consume the ChangeNotifierProvider.
For me below simple implementation work perfectly.
my main.dart file code is as below...
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'providers/app_provider.dart';
import 'providers/favorites_provider.dart';
import 'providers/comments_provider.dart';
import 'providers/home_provider.dart';
import 'providers/details_provider.dart';
import 'providers/gallery_provider.dart';
import 'providers/chat_provider.dart';
import 'ui/splash.dart';
import 'helper/constants.dart';
import 'ui_user/login.dart';
void main() {
runApp(
MultiProvider(
providers: [
ChangeNotifierProvider(create: (_) => AppProvider()),
ChangeNotifierProvider(create: (_) => GalleryProvider()),
ChangeNotifierProvider(create: (_) => CommentsProvider()),
ChangeNotifierProvider(create: (_) => ChatProvider()),
ChangeNotifierProvider(create: (_) => HomeProvider()),
ChangeNotifierProvider(create: (_) => DetailsProvider()),
ChangeNotifierProvider(create: (_) => FavoritesProvider()),
],
child: MyApp(),
),
);
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Consumer<AppProvider>(
builder: (BuildContext context, AppProvider appProvider, Widget child) {
return MaterialApp(
key: appProvider.key,
debugShowCheckedModeBanner: false,
navigatorKey: appProvider.navigatorKey,
title: Constants.appName,
theme: appProvider.theme,
home: appProvider.isLogin == "0" ? LoginPage() : Splash(),
);
},
);
}
}
And my app_provider.dart as below...
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:shared_preferences/shared_preferences.dart';
import '../helper/constants.dart';
class AppProvider extends ChangeNotifier {
AppProvider() {
checkTheme();
}
String isLogin = "0";
ThemeData theme = Constants.lightTheme;
Key key = UniqueKey();
GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
void setKey(value) {
key = value;
notifyListeners();
}
void setNavigatorKey(value) {
navigatorKey = value;
notifyListeners();
}
void setTheme(value, c) {
theme = value;
SharedPreferences.getInstance().then((prefs) {
prefs.setString("theme", c).then((val) {
SystemChrome.setEnabledSystemUIOverlays(SystemUiOverlay.values);
SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle(
statusBarColor:
c == "dark" ? Constants.darkPrimary : Constants.lightPrimary,
statusBarIconBrightness:
c == "dark" ? Brightness.light : Brightness.dark,
));
});
});
notifyListeners();
}
ThemeData getTheme(value) {
return theme;
}
Future<ThemeData> checkTheme() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
ThemeData t;
String r =
prefs.getString("theme") == null ? "light" : prefs.getString("theme");
isLogin = prefs.getString("isLogin") == null? "0" : prefs.getString("isLogin");
if (r == "light") {
t = Constants.lightTheme;
setTheme(Constants.lightTheme, "light");
} else {
t = Constants.darkTheme;
setTheme(Constants.darkTheme, "dark");
}
return t;
}
}
This solution is working very well for me. Hope this will help you too...

Related

Flutter routing after v2 upgrade null check operator

I have recently updated my flutter project to the V2 sdk I am having an issue with the null check operator for my routing when I try and click on a route the following error comes up Null check operator used on a null value I understand what it means but for some reason my navigatorkey.currentState keeps coming back as null even though it is set in the MaterialApp
Below is my navigation file
import 'package:flutter/material.dart';
class Navigation {
static Navigation instance = Navigation();
GlobalKey<NavigatorState>? navigatorKey = new GlobalKey<NavigatorState>();
Future<dynamic> navigateToReplacement(String newRoute) {
return navigatorKey!.currentState!.pushReplacementNamed(newRoute);
}
Future<dynamic> navigateTo(String newRoute) {
return navigatorKey!.currentState!.pushNamed(newRoute);
}
Future<dynamic> navigateToPage(MaterialPageRoute newRoute) {
return navigatorKey!.currentState!.push(newRoute);
}
goBack() {
return navigatorKey!.currentState!.pop();
}
}
And this is my main.dart file before the upgrade I was not having this issue
import 'package:eventapp/screens/auth/forgotPassword.dart';
import 'package:eventapp/screens/events/add_event.dart';
import 'package:eventapp/services/messaging.dart';
import 'package:flutter/material.dart';
import 'package:eventapp/services/navigation.dart';
import 'package:eventapp/screens/auth/sign_in.dart';
import 'package:eventapp/screens/auth/register.dart';
import 'package:eventapp/screens/home/home.dart';
import 'package:overlay_support/overlay_support.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:get_it/get_it.dart';
GetIt getIt = GetIt.instance;
void main() async {
getIt.registerLazySingleton(() => Navigation());
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
runApp(MyApp());
}
class MyApp extends StatelessWidget {
final PushNotificationsManager _pushnotifications =
PushNotificationsManager();
final Navigation _navigation = Navigation();
#override
Widget build(BuildContext context) {
_pushnotifications.init();
return OverlaySupport(
child: MaterialApp(
navigatorKey: _navigation.navigatorKey,
theme: ThemeData(
brightness: Brightness.dark,
primaryColor: Color.fromRGBO(112, 17, 15, 1),
backgroundColor: Color.fromRGBO(14, 12, 10, 5),
),
onGenerateRoute: (routeSettings) {
switch (routeSettings.name) {
case 'login':
return MaterialPageRoute(builder: (context) => SignIn());
case 'register':
return MaterialPageRoute(builder: (context) => Registration());
case 'forgotPassword':
return MaterialPageRoute(builder: (context) => ForgotPassword());
case 'addEvent':
return MaterialPageRoute(builder: (context) => AddEvent());
default:
return MaterialPageRoute(builder: (context) => EventList());
}
},
initialRoute: 'login',
));
}
}
Could someone tell me if there is something wrong here?
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const SignforgotPassword()));
Did you set navigatorKey in your MaterialApp?
Should be something like navigatorKey: NavigationService.navigatorKey

Flutter Error: Could not find the correct Provider<User> above this Wrapper Widget

After updating/converting a program to null-safety, i'm presented with the following error:
Error: Could not find the correct Provider above this Wrapper 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 Wrapper 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>()),
}
),
}
If none of these solutions work, consider asking for help on StackOverflow:
https://stackoverflow.com/questions/tagged/flutter
The error also points to the "relevant error-causing widget" for me it was my call to my Wrapper(), Highlighted below:
The relevant error-causing widget was:
Wrapper Wrapper:file:///home/AlphaUser/AndroidStudioProjects/test/lib/main.dart:78:20
The stack had more lines I didn't think were relevant. (I can add if needed)
Current Code in use:
Main.dart:
import 'package:firebase_core/firebase_core.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:test/screens/authenticate/test_forgot.dart';
import 'package:test/screens/authenticate/test_login.dart';
import 'package:test/screens/authenticate/test_signup.dart';
import 'package:test/screens/faq_screen.dart';
import 'package:test/screens/review_purchase_screen.dart';
import 'package:test/screens/purchased_products_screen.dart';
import 'package:test/screens/search_products_screen.dart';
import 'package:test/screens/test_postImage.dart';
import 'package:test/wrapper/wrapper.dart';
import "package:provider/provider.dart";
import 'package:test/services/auth.dart';
import 'models/user.dart';
import 'package:test/screens/product_detail_screen.dart';
import 'package:test/providers/products_provider.dart';
import 'package:test/providers/shippments_provider.dart';
import 'package:test/providers/orders.dart';
import 'package:test/screens/user_products_screen.dart';
import 'package:test/screens/edit_product_screen.dart';
import 'package:test/screens/product_checkout_screen.dart';
import 'package:test/screens/test_feed.dart';
import 'package:test/screens/profile_screen.dart';
import 'package:test/home/home.dart';
import 'package:test/home/homeUser.dart';
import 'package:flutter_stripe/flutter_stripe.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
FirebaseApp app = await Firebase.initializeApp();
SystemChrome.setPreferredOrientations(
[DeviceOrientation.portraitUp, DeviceOrientation.portraitDown]);
Stripe.publishableKey =
'stripe_publishableKey';
await Stripe.instance.applySettings();
runApp(MyApp());
}
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
ChangeNotifierProvider(
create: (ctx) => Shippments(),
),
ChangeNotifierProvider(
create: (ctx) => Orders(),
),
ChangeNotifierProvider(
create: (ctx) => Products(),
),
],
child: StreamProvider<User>.value(
value: AuthService().user,
initialData: null,
child: new MaterialApp(
title: 'Test',
theme: ThemeData(
primarySwatch: Colors.grey,
// accentColor: Colors.green,
),
debugShowCheckedModeBanner: false,
//* Login Options **//
home: Wrapper(), //***<-- Error points to this line.***
//home: Home(), //<-- **Works Fine if I use this instead of Wrapper().** //
//* Login Options **//
routes: {
TestLogin.id: (context) => TestLogin(),
TestSignup.id: (context) => TestSignup(),
TestForgot.id: (context) => TestForgot(),
ProductDetailScreen.routeName: (context) => ProductDetailScreen(),
UserProductsScreen.routeName: (context) => UserProductsScreen(),
EditProductScreen.routeName: (context) => EditProductScreen(),
TestPostImageScreen.routeName: (context) => TestPostImageScreen(),
ProductCheckoutScreen.routeName: (context) =>
ProductCheckoutScreen(),
PurchasedProductsScreen.routeName: (context) =>
PurchasedProductsScreen(),
SearchProductsScreen.routeName: (context) => SearchProductsScreen(),
TestFeed.routeName: (context) => TestFeed(),
ReviewPurchaseScreen.routeName: (context) => ReviewPurchaseScreen(),
ProfileScreen.routeName: (context) => ProfileScreen(),
FAQScreen.routeName: (context) => FAQScreen()
},
),
),
);
}
}
wrapper.dart
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:test/home/home.dart';
import 'package:test/home/homeUser.dart';
import 'package:test/models/user.dart' as model;
import '../screens/authenticate/test_login.dart';
import 'package:http/http.dart' as http;
import 'package:flutter_spinkit/flutter_spinkit.dart';
class Wrapper extends StatefulWidget {
#override
_WrapperState createState() => _WrapperState();
}
class _WrapperState extends State<Wrapper> {
get lists => null;
bool currentAdmin;
bool loading = false;
Widget build(BuildContext context) {
final user = Provider.of<User>(context);
return FutureBuilder(
future: _checkUser(user),
builder: (context, snapshot) {
String profile = snapshot.data;
print("profile!!!: $profile");
if (profile == 'true') {
print("Snapshot has data: ${snapshot.data}");
return Home();
} else if (profile == 'false') {
print("Snapshot has no data: ${snapshot.data}");
return HomeUser();
} else if (user == null) {
return TestLogin();
} else {
//return CircularProgressIndicator();
return SpinKitFadingCube(
color: Colors.black,
size: 25.0,
);
}
//return null;
},
);
}
_checkUser(User user) async {
...
} //_checkUser()
}
User.dart
class User {
final String uid;
User({this.uid});
}
Just want to add that if I replace Wrapper() with Home() the program works, but that will bypass my "user login/register/forgot password" functionality.
Any help or guidance will be much appreciated.
you are using the User Provider and didn't create the user provider yet
add the User Provider in the MultiProvider in main
return MultiProvider(
providers: [
ChangeNotifierProvider(
create: (ctx) => Shippments(),
),
ChangeNotifierProvider(
create: (ctx) => Orders(),
),
ChangeNotifierProvider(
create: (ctx) => Products(),
),
ChangeNotifierProvider( // add this
create: (ctx) => User(),
),
],
child: StreamProvider<User>.value(

Flutter main.dart initialRout is not Working

I'm New in this FrameWork and here initial Rout is not Accepting the Loggin Session value Please help me with this. I tried to add Home with the splash screen but that also not working I'm not getting What's wrong in this.
This is my main Page
Future main() async {
SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle(
statusBarColor: Colors.transparent,
statusBarIconBrightness: Brightness.dark,
));
runApp(Phoenix(child: AmericanCuisine()));
}
class AmericanCuisine extends StatefulWidget {
#override
_AmericanCuisineState createState() => _AmericanCuisineState();
}
class _AmericanCuisineState extends State<AmericanCuisine> {
bool isLoggedIn;
#override
void initState() {
super.initState();
getData();
}
getData() async {
WidgetsFlutterBinding.ensureInitialized();
SharedPreferences storage = await SharedPreferences.getInstance();
setState(() {
isLoggedIn = storage.getBool("loggedIn");
});
}
#override
Widget build(BuildContext context) {
return MultiBlocProvider(
providers: [
BlocProvider<LanguageCubit>(
create: (context) => LanguageCubit(),
),
BlocProvider<ThemeCubit>(
create: (context) => ThemeCubit(),
),
],
in this page after using BlockBuilder how i To give the Initial Route
child: BlocBuilder<ThemeCubit, ThemeData>(
builder: (_, theme) {
return BlocBuilder<LanguageCubit, Locale>(
builder: (_, locale) {
return MaterialApp(
localizationsDelegates: [
const AppLocalizationsDelegate(),
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
],
supportedLocales: [
const Locale('en'),
],
locale: locale,
theme: theme,
//This initial rout is not working.
initialRoute: isLoggedIn == false ?'/': '/homeOrderAccount',
routes: {
// When navigating to the "/" route, build the FirstScreen widget.
'/': (context) => OpeningScreen(),
'/homeOrderAccount': (context) => HomeOrderAccount(),
},
);
},
);
},
),
);
}
}
You can't use initialRoute with routes map either delete '/' from the routes map or delete the initialRoute:

How can I initialize my app with provider?

I choose to use provider as my state management so I saw I have to use Multi provider.
My struggle is how to architect my code that I can initialize all the data I need when my app first run and give the providers to the multi provider.
Provider example
import 'package:cron/cron.dart';
import 'package:flutter/material.dart';
import 'package:web_app/models/fixture.dart';
import 'package:web_app/services/fixture_service.dart';
class HighlightsProvider extends ChangeNotifier {
final List<Fixture> _highlights = [];
List<Fixture> get() => _highlights;
Future<void> fetchHighlights() async {
try {
List<Fixture> highlightFixtures = [];
final response = await FixtureService().getAppHighlightFixtures();
[...response].asMap().forEach((index, element) {
highlightFixtures.add(new Fixture.fromJson(element));
});
_highlights.clear();
_highlights.addAll(highlightFixtures);
notifyListeners();
} catch (e) {
print('error');
print(e);
}
}
runJob(cron) {
cron.schedule(Schedule.parse('* * * * *'), () async {
fetchHighlights();
print('fetch highlights every one minute');
});
}
}
Let's say this class will get all my providers and initialize theme:
class InitializeApp {
final cron = Cron();
Future run(HighlightsProvider highlightsProvider) async {
return Future.wait([
initiakizeHighlights(highlightsProvider),
]);
}
Future initiakizeHighlights(HighlightsProvider highlightsProvider) async {
highlightsProvider.runJob(cron);
await highlightsProvider.fetchHighlights();
}
}
Then I have to deliver those provider to the multi provider:
void main() async {
final highlightsProvider = HighlightsProvider();
await InitializeApp().run(highlightsProvider);
print('ready');
runApp(MyApp(highlightsProvider: highlightsProvider));
}
class MyApp extends StatelessWidget {
final highlightsProvider;
const MyApp({Key key, this.highlightsProvider}) : super(key: key);
#override
Widget build(BuildContext context) {
print('build');
return MultiProvider(
providers: [
ChangeNotifierProvider<HighlightsProvider>.value(
value: highlightsProvider,
)
],
child: MaterialApp(
title: 'tech',
theme: ThemeData(
primarySwatch: Colors.amber,
brightness: Brightness.light,
),
routes: <String, WidgetBuilder>{
'/': (BuildContext context) {
return MyHomePage(title: 'Flutter Demo Home Page');
}
}),
);
}
}
Normally you just wrap your MaterialApp with the MultiProvider, then you already have access to all Providers you will define.
Widget build(BuildContext context) {
return MultiProvider(
providers: [
StreamProvider<RecipeStreamService>.value(value: RecipeStreamService().controllerOut)
],
child: MaterialApp(
debugShowCheckedModeBanner: false,
title: 'Home Food',
routes: {
'/register': (BuildContext context) => RegisterPage(),
'/login': (BuildContext context) => LoginPage()
},
),
home: HomePage(title: 'Home'),
),
);
}

TextFormField does not reflect initialValue from a ChangeNotifierProvider

I am trying to set the initialValue of my TextFormField from my provider. This works but not on inital load of my screen. For this to work, I need to go to previous screen, then come back. Here are the relevant source codes.
User class:
class User with ChangeNotifier {
String name;
String email;
User() {
_deserialiseUser();
}
}
void _deserialiseUser() async {
final prefs = await SharedPreferences.getInstance();
final String jsonUser = prefs.getString('user');
if (jsonUser != null) {
var ju = jsonDecode(jsonUser);
name = ju['name'];
email = ju['email'];
notifyListeners();
}
}
Main class:
void main() => runApp(
MultiProvider(
providers: [
ChangeNotifierProvider(create: (context) => User()),
...
],
child: MyApp(),
),
);
The affected screen:
class AccountScreen extends StatefulWidget {
#override
_AccountScreenState createState() => _AccountScreenState();
}
class _AccountScreenState extends State<AccountScreen> {
User user;
#override
Widget build(BuildContext context) {
user = context.watch<User>();
return Scaffold(
appBar: AppBar(
title: Text('Account'),
),
body: TextFormField(
initialValue: user.name,
),
);
}
}
If I replace my TextFormField with Text, this works fine. What am I missing?
EDIT: Although I already have the answer, I will still accept answers that can explain the root cause of the issue.
I solved the issue by not lazily loading my User provider like so:
void main() => runApp(
MultiProvider(
providers: [
ChangeNotifierProvider(
create: (context) => User(),
lazy: false,
),
],
child: MyApp(),
),
);