ChangeNotifierProxyProvider getting null value - flutter

I am new to flutter.
In my application locale information is found when the user login.
So the idea is when the user login, it will pass the locale to AppLanguage.
I have written ChangeNotifierProxyProvider to get the locale inside authentication information and create a AppLanuage object
In the ChangeNotifierProxyProvider I am getting appLang as null. auth object is correctly NOT null.
What I don't understand why I am getting null?
I did create it here right?
create: (_) => AppLanguage(),
shouldn't it come as a parameter for the update?
Widget build(BuildContext context) {
return MultiProvider(
providers: [
ChangeNotifierProvider.value(value: Auth()),
ChangeNotifierProxyProvider<Auth, AppLanguage>(
create: (_) => AppLanguage(),
update: (ctx, auth, appLang) {
print(auth);
print(appLang);
}
//appLang.setLocale(auth == null ? 'en' : auth.language),
),
],
child: Consumer2<Auth, AppLanguage>(
builder: (ctx, auth, lang, child) => MaterialApp(
title: 'Test App',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
locale: lang.appLocal,
supportedLocales: [
const Locale('en', 'US'),
const Locale('ja', ''),
],
localizationsDelegates: [
AppLocalizations.delegate,
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
],
home: LandingView(),
),
),
);
}

I would try something like:
ChangeNotifierProxyProvider<Auth, AppLanguage>(
create: (_) => AppLanguage(),
update: (ctx, auth, appLang) => appLang..update(auth),
),
class AppLanguage with ChangeNotifier {
void update(Auth auth) {
// Do some custom work based on myModel that may call `notifyListeners`
}
}
That way your child will be able to get the correct updated values when they're available.
You can check more how to deal properly with that on the provider docs.

Related

flutter: Navigation routes not working on authentication

I am new to flutter and trying to get this to work. I have a Welcome() page, which has two buttons (SignUp and SignIn), which take you to SignUp() and SignIn() pages. After successfully authenticating, the app should navigate to Home(), but it does not. Neither does it go back to Welcome() page if user logs out. Also, if the app is started and user was already logged in, it doesn't automatically go to Home(), it stays at Welcome(). What am I doing wrong here?
I am using a StreamProvider like so
if (snapshot.connectionState == ConnectionState.done) {
return StreamProvider<User>.value(
value: AuthService().user, child: Wrapper());
}
And my wrapper looks like this:
class Wrapper extends StatelessWidget {
const Wrapper({Key key}) : super(key: key);
#override
Widget build(BuildContext context) {
final user = Provider.of<User>(context);
if (user != null)
print(user.id);
else
print("User is null");
return MaterialApp(initialRoute: '/', routes: {
'/': (context) => user == null ? Welcome() : Home(),
'/signup': (context) => SignUp(),
'/signin': (context) => SignIn()
'/profile': (context) => Profile(),
'/edit': (context) => Edit()
});
}
This prints the user ID, which means the user is not null, but it does not navigate to Home().
I'm not dictating you what to do but i will just show you how i usually do.
The concept is simple. When the user successfull login or signup, you must save this information in a shared_preference. It may be like auth : true.
You must also wait on the app launching process to see if the user already login or not and based on that, you can navigate to another screen.
To check at startup if user is authenticated, you must read the auth property you previously added in shared_preference. Look at this code :
void main() async {
var mapp;
var routes = <String, WidgetBuilder>{
'/initialize': (BuildContext context) => Initialize(),
'/register': (BuildContext context) => Register(),
'/home': (BuildContext context) => Home(),
};
print("Initializing.");
WidgetsFlutterBinding.ensureInitialized();
await SharedPreferencesClass.restore("initialized").then((value) {
if (value) {
mapp = MaterialApp(
debugShowCheckedModeBanner: false,
title: 'AppName',
theme: ThemeData(
primarySwatch: Colors.blue,
),
routes: routes,
home: Home(),
);
} else {
mapp = MaterialApp(
debugShowCheckedModeBanner: false,
title: 'AppName',
theme: ThemeData(
primarySwatch: Colors.blue,
),
routes: routes,
home: Initialize(),
);
}
});
print("Done.");
runApp(mapp);
}
This code is so self explanatory. You can adapt it to your edge.
To navigate to a new screen it is simple, just use this code :
Navigator.push(
context,
MaterialPageRoute(builder: (context) => SecondRoute()),
);

How to add multi notifier provider in 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()),
],

Flutter - app localization using intl translations for messages inside Cubit/ Bloc

I am working on a multilingual app in Flutter.
I had no problem with implementing the localized strings in widgets/ screens following the official docs: https://flutter.dev/docs/development/accessibility-and-localization/internationalization.
But ... my app does a lot of calls to an API which are handled behind the scenes by appropriate Cubits and Repositories. For these calls and other deeper logic I would like to provide status messages in appropriate languages (e.g. for snackbars). The problem I face is that I cannot access localized strings inside the Cubits to provide messages to the state. Even if I try to pass the context to the Cubit it does not see them.
Anyone have an idea please? I would prefer following the official approach and not having to totally refactor the app ...
Thanks in anticipation!
Somebody helped me with this.
The solution is simple(ish).
You have to pass appLocalizations themselves into the Cubits. Here is my main.dart:
runApp(
MaterialApp(
localizationsDelegates: [
AppLocalizations.delegate,
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
],
supportedLocales: [
const Locale('en', ''),
const Locale('pl', ''),
],
title: 'MySuperApp',
debugShowCheckedModeBanner: false,
theme: ThemeData(
primarySwatch: Colors.orange,
accentColor: Colors.deepOrangeAccent,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
initialRoute: '/',
routes: {
UserAuthScreen.routeName: (context) => const UserAuthScreen(),
HomePage.routeName: (context) => HomePage(),
...
},
builder: (context, child) {
final appLocalizations = AppLocalizations.of(context); //IMPORTANT
return MultiBlocProvider(
providers: [
BlocProvider<ConstantsCubit>(
lazy: true,
create: (context) => ConstantsCubit(
constantsRepository: ConstantsRepository(),
),
),
BlocProvider<UserAuthCubit>(
lazy: true,
create: (context) => UserAuthCubit(
localizations: appLocalizations, //THIS IS WHERE THE MAGIC HAPPENS
repository: UserAuthRepository(),
),
),
BlocProvider<DoerInfoCubit>(
lazy: true,
create: (context) => DoerInfoCubit(
doerInfoRepository: DoerInfoRepository(),
userAuthCubit: BlocProvider.of<UserAuthCubit>(context),
)),
...
],
child: child,
);
},
home:
BlocBuilder<UserAuthCubit, UserAuthState>(builder: (context, state) {
if (state is UserAuthLogged) {
return HomePage();
} else {
return const UserAuthScreen();
}
}),
),
);
The UserAuthCubit declaration looks like this:
class UserAuthCubit extends Cubit<UserAuthState> {
final UserAuthRepository repository;
final AppLocalizations localizations; //THIS IS WHERE THE MAGIC HAPPENS
UserAuthCubit({
#required this.repository,
#required this.localizations,
}) : super(const UserAuthInitial()) {
getUserAuthState();
}
IMPORTANT - to make sure the solutions works - AppLocalizatons have to be declared before the Cubits in the main.dart.
ALSO - the app adopts new localizations in the widgets straight away after language change. However with this setup I was not able to achieve this for Cubits. They need a restart of the app. In real life scenario this should not be a problem ... I guess.
It's easy to do with the easy_localization package. The only downside is code generation to be done on every string change. Otherwise it doesn't need a context to get a string, like so:
LocaleKeys.app_title.tr()
Check out the whole tutorial on how to setup and use EasyLocalization.

Why does flutter localization not work properly?

I want to add the tr tag to the Flutter localization section. I am
getting an error although there is support for Tr. The error is as
follows; error: The element type 'Locale (where Locale is defined in
C:\Users\StatTark\AppData\Roaming\Pub\Cache\hosted\pub.dartlang.org\intl-0.16.1\lib\src\locale.dart)'
can't be assigned to the list type 'Locale (where Locale is defined in
C:\flutter\bin\cache\pkg\sky_engine\lib\ui\window.dart)'.
error: Abstract classes can't be instantiated.
(instantiate_abstract_class at [ajanda] lib\pages\mainmenu.dart:36)
I do not understand if I am making a mistake in use, I will be glad if you help.
class MainMenu extends StatelessWidget {
MainMenu({Key key}) : super(key: key);
final _sdb = SettingsDbHelper();
#override
Widget build(BuildContext context) {
return FutureBuilder(
future: _sdb.getSettings(),
builder: (context, snapshot) {
if (snapshot.data == null) {
return MaterialApp(
localizationsDelegates: [
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate
],
supportedLocales: [Locale('en','US'),Locale('tr','')], //The error is here
debugShowCheckedModeBanner: false,
home: Scaffold(
body: Center(
child: Text(proTranslate["Yükleniyor....."][Language.languageIndex]),
),
),
);
} else {
Language.languageIndex = snapshot.data[0].language;
return DynamicTheme(
defaultBrightness: Brightness.light,
data: (brightness) => ThemeData(
brightness: brightness,
fontFamily: snapshot.data[0].fontName,
floatingActionButtonTheme: FloatingActionButtonThemeData(
foregroundColor: Colors.green,
),
),
themedWidgetBuilder: (context, theme) {
return MaterialApp(
localizationsDelegates: [
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate
],
supportedLocales: [Locale('en','US'),Locale('tr','')],
debugShowCheckedModeBanner: false,
theme: theme,
home: MainMenuBody(
warning: snapshot.data[0].warning,
),
// navigatorKey: navigatorKey,
);
});
}
},
);
}
}
class MainMenuBody extends StatefulWidget {....}
I'm coming a bit late but I just fixed this same issue I had.
I'm pretty sure your file is importing intl/locale.dart instead of flutter/material.dart as both define a Local type.
To fix it, just replace your import at the top of the file from:
import 'package:intl/locale.dart';
to
import 'package:flutter/material.dart';
and you should be OK.
Few suggestions you can try/test, see below.
Replace [Locale('en','US'),Locale('tr','')], with [Locale('en'),Locale('tr')],
init list of supported locales first and use it accordingly.
// init before build
final List<Locale> appSupportedLocales = [
Locale('en'),
Locale('tr')
];
// ...
// then use it like this
supportedLocales: appSupportedLocales,

Flutter Local routes issue

I have a small problem with a flutter local app. I am trying to have 2 routes depending on device languageCode. When I build the app, that second option are launched. If I save the app, the right route are picked. I think the
final lang = locale.languageCode;
Do not have a value when this is called:
"/newsfeed": lang == "da"
? (BuildContext context) => News()
: (BuildContext context) => NewsEN(),
The print statement I have in the code have the right value, so are a bit lost Here are the code:
class MyApp3 extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
// List all of the app's supported locales here
supportedLocales: [
Locale('en', 'US'),
Locale('da', 'DK'),
],
// These delegates make sure that the localization data for the proper language is loaded
localizationsDelegates: [
// A class which loads the translations from JSON files
AppLocalizations.delegate,
// Built-in localization of basic text for Material widgets
GlobalMaterialLocalizations.delegate,
// Built-in localization for text direction LTR/RTL
GlobalWidgetsLocalizations.delegate,
],
// Returns a locale which will be used by the app
localeResolutionCallback: (locale, supportedLocales) {
// Check if the current device locale is supported
for (var supportedLocale in supportedLocales) {
//print(locale.languageCode);
final lang = locale.languageCode;
print(lang);
if (supportedLocale.languageCode == locale.languageCode &&
supportedLocale.countryCode == locale.countryCode) {
return supportedLocale;
}
}
// If the locale of the device is not supported, use the first one
// from the list (English, in this case).
return supportedLocales.first;
},
home: MyHomePage(),
routes: <String, WidgetBuilder> {
//"/Knowledge_pick": (BuildContext context) => SplashScreen(),
"/calculator_pick": (BuildContext context) => Pickcalculator(),
//"/receipe_pick": (BuildContext context) => Receipemenu(),
"/receipe_pick": (BuildContext context) => Wrapper(),
"/newsfeed": lang == "da"
? (BuildContext context) => News()
: (BuildContext context) => NewsEN(),
Hope someone can help me with this problem.
Honestly I don't see why this wouldn't work but what you should try to do is setup two different routes, one from 'da' and one for 'en'
"/newsfeedDA": (BuildContext context) => News(),
"/newsfeedEN": (BuildContext context) => NewsEN(),
and have your logic for selecting them in the initState or constructor of MyHomePage().