Problem in displaying localized labels in Dart - flutter

I am not able to set localization in my app.
I am trying to add language settings and associated localization in my app. I am able to get-set the language option. I am using 'intl' plug-in for internationalization. My code looks like below in pretty much all the UI .dart files.
AppTranslations.of(context).accountNumber +
" ${accountDetails.accountNumber}",
The getters is set as :
String get accountNumber => _text("account_number");
String _text(String key) {
return _localisedValues[key] ?? _defaultLocaleValues[key];
}
I've also placed json files containing localized labels in 3 different languages. However, it seems there is some instantiation problem of the locazation plug-in. The code doesn't go the getter line.
Any help would be highly appreciated.

AppTranslations.of(context) is a standard way of accessing the localised labels. You are right about the instantiation. If the program doesn't go to the getter line them it means, there's a problem in somewhere in the initial part of the code. It could be in the main.dart.
Check where you are initialising LocalStorageProvider(). In case it is not initialised then that's the problem. Assuming you are using a MaterialApp, try the below suggestion then :
Wrap the MaterialApp with LocalStorageProvider(). I mean, in the main widget build, return LocalStorageProvider() and pass your existing code of MaterialApp() as a child to it. Sample below (Please ignore the theme etc since I just copied the code from one of my app) :
#override
Widget build(BuildContext context) {
LocalStorage localStorage = LocalStorage();
return LocalStorageProvider(
localStorage: localStorage,
child: LocaleProvider(
localStorage: localStorage,
localeWrapper: LocaleWrapper(),
child: Builder(
builder: (context) {
return AnimatedBuilder(
animation: LocaleProvider.of(context).localeWrapper,
builder: (context, _) {
return MaterialApp(
onGenerateTitle: (context) =>
AppTranslations.of(context).appName,
locale: LocaleProvider.of(context).locale,
title: "App Title",
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MapsDemo(),
localizationsDelegates: [
AppTranslationsDelegate(
LocaleProvider.of(context).supportedLanguagesCodes,
),
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
],
supportedLocales: LocaleProvider.of(context).supportedLocales,
);
},
);
},
),
),
);
}

Related

Flutter - Error in hot reload using lazy internationalization

I'm building an application that uses lazy internationalization, this way there will be no translation files in the application and all translations will be fetched from the internet when a new page is opened. For that I am using a localization cubit.
Each screen of my application is divided into a "view" that receives the translated messages as a parameter, a "cubit" that contains the cubit screen and its states, and a "container" that contains the BlocProvider for the cubit and the screen.
For now my app starts in the presentation screen, after that it goes to the login screen and finally goes to the home screen.
So in the main file, instead of using the presentation screen directly, I use the localization container and the presentation container comes as its child:
return MaterialApp(
title: 'My App',
theme: myTheme(context),
debugShowCheckedModeBanner: false,
home: LocalizationContainer(
child: PresentationContainer(),
),
);
The PresentationContainer is composed this way:
class PresentationContainer extends BlocContainer {
#override
Widget build(BuildContext context) {
return BlocProvider(
create: (_) => PresentationCubit(),
child: I18NLoadingContainer(
language: BlocProvider.of<CurrentLocaleCubit>(context).state,
viewKey : "Presentation",
creator: (messages) => PresentationView(PresentationViewLazyI18N(messages)),
),
);
}
}
So in the container I have a BlocProvider with PresentationCubit and I18NLoadingContainer as a child.
I18NLoadingContainer just obtains the transalted messages according to the language provided and the screen name, that is "Presentation" in this case. The translated messages are returned in the variable messages, so this messages are passed as parameter to the screen.
If I use this only for my presentation screen everything works fine, but the issue comes when I need to open a new page.
After the presentation screen I need to open the login screen. So in the PresentationView I have the following function when the user clicks the button to open the login screen:
void _goToLogin(BuildContext blocContext) {
Navigator.of(blocContext).pushReplacement(
MaterialPageRoute(
builder: (context) => BlocProvider.value(
value: BlocProvider.of<CurrentLocaleCubit>(blocContext),
child: LoginContainer(),
),
),
);
}
And the LoginContainer works exaclty as the PresentationContainer:
class LoginContainer extends BlocContainer {
#override
Widget build(BuildContext context) {
return BlocProvider(
create: (_) => LoginCubit(),
child: I18NLoadingContainer(
language: BlocProvider.of<CurrentLocaleCubit>(context).state,
viewKey : "Login",
creator: (messages) => LoginView(LoginViewLazyI18N(messages)),
),
);
}
}
If I keep in the presentation screen and use the hot reload everything works fine, but if I open a new screen using this method, I got the following error when try to use hot reload:
The following _CastError was thrown building Builder(dirty): Null
check operator used on a null value
I'm not sure your LoginContainer is still wrapped by the LocalizationContainer when you change the route. I would suggest you to provide a CurrentLocaleCubit above the MaterialApp widget and check whether it's working or not. I think you're loosing a CurrentLocaleCubit instance

Couldn't find the correct provider

I am doing an experiment, where I want to make two similar apps with single source code.
I am trying to make an "adaptive" State on top of the widget tree, and this state (ChangeNotifier) depends on the application (isApp1() determines which app is this ).
here's the code that I'm using:
ChangeNotifierProvider(
create: (context) {
return isApp1() ? AppState1() : AppState2();
},
child: MaterialApp(
onGenerateRoute: isAdminApp()
? App1Router.generateRoute
: App2Router.generateRoute,
initialRoute: "/",
),
);
when I try to read the state using Provider.of in the low levels of the tree I am facing the error:
Error: Could not find the correct Provider<AppState1> above this Widget
Note: when I put AppState1() or AppState2() directly instead of isApp1() ? AppState1() : AppState2() I don't face this error.

Flutter initial routes with parameters

I need to pass an argument for my initialRoute. I found this issue and tried it like this:
initialRoute: AuthService.isLoggedIn() ? Views.home : Views.firstStart,
onGenerateInitialRoutes: (String initialRouteName) {
return [
AppRouter.generateRoute(
RouteSettings(
name: AuthService.isLoggedIn() ? Views.home : Views.firstStart,
arguments: notificationPayloadThatLaunchedApp,
),
),
];
},
onGenerateRoute: AppRouter.generateRoute,
This almost works. The problem I have is that somehow after calling this...
Navigator.pushReplacementNamed(
context,
Views.loading,
);
... my Multiprovider which is a parent of my GetMaterialApp is being called again which crashed my app because I call a function when initializing my providers:
#override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
ChangeNotifierProvider(
create: (context) {
var dataProvider = DataProvider();
dataProvider.init( // <- this is called again which should not happen
context,
);
return dataProvider;
},
),
],
child: GetMaterialApp(
title: 'Flutter Boilerplate',
navigatorKey: Get.key,
initialRoute: AuthService.isLoggedIn() ? Views.home : Views.firstStart,
onGenerateInitialRoutes: (String initialRouteName) {
return [
AppRouter.generateRoute(
RouteSettings(
name: AuthService.isLoggedIn() ? Views.home : Views.firstStart,
arguments: notificationPayloadThatLaunchedApp,
),
),
];
},
onGenerateRoute: AppRouter.generateRoute,
),
);
}
I feel like the way how I pass the initial argument is wrong. Is there any other/better way to get this done? Let me know if you need any more info!
I think you misunderstand the usage of new API onGenerateInitialRoutes cause by the name it should load without the calling of
Navigator.pushReplacementNamed(
context,
Views.loading,
);
API at all. if you call this route from another widget, it means this is already the second route already. it should be the default route for your application. so it makes no sense to give a parameter to an initial route at all.
since you have used Provider Package, it is better to just get whatever argument(parameter) that you want to send via Provider API itself.
if you want to give hardcoded data, then just treat it as a normal Widget class.
home: MyHomePage(name:"parameter name",data:DataHome()), :
GetMaterialApp(
title: 'Flutter Boilerplate',
navigatorKey: Get.key,
home: MyHomePage(name:"parameter name",data:DataHome()),
onGenerateRoute: AppRouter.generateRoute,
);
take note that if you calling Navigator.pushReplacementNamed API . your provider will be gone cause of the Flutter Widget Tree most Navigator API will create a new Widget tree level from the root. since the provider only provides for all its child only. so you cant use any provider data since you have a different ancestor.
so if you only have one page to go I suggest you use the home attribute in MaterialApp API cause it will treat your initial page as a child and Provider API can receive the data cause up in the Widget tree, it has an Ancestor Provider API:
MultiProvider(
providers: [
ChangeNotifierProvider(
if you want to move between pages via navigator after the main page.
consider using NestedNavigator so flutter will not create a new route from the root of the widget tree. so you still can access Provider API's data.
guessing from your variable name. I assume that you want to handle a notification
routing, check this deeplink

Flutter get context value from outside context

I am very new to Flutter so I'm not sure if my train of thoughts even make sense. I'm currently using a package called EasyLocalization to localize my app. In doing so, I am trying to reference the current locale of my application which is set by EasyLocalization in a service class completely independent of widgets but the only methods offered by the package was to reference the context
In their example, the following code works
print(context.locale.toString());
However, since I want the value in a "service" class that doesn't make use of widgets, I'm not able to call on any context at all. So something like this code works on my widget but not in an independent service class since context doesn't exist there
var currentLocale = EasyLocalization.of(context)?.locale ?? 'en';
I've also tried some other code to get the localization but they turn out different from the actual localization my app is sync'ed to. For example when my app is running off 'zh' in EasyLocalization, other methods such as the ones below only return 'en-US'
print(Intl().locale);
print(Intl.getCurrentLocale());
One way I've gotten it to partially work is to create a function inside my widget that sets a global value when clicked and then reference that value but it feels "hacky" and isn't sufficient for my use-case where data gets loaded on application start which is then passed through a translation function using the context locale. Most other search results also only turn up information for navigation and snackbars which don't seem to help my use-case so I'm currently out of ideas and turning to SO for help.
Below is my MaterialApp() if it helps
Widget build(BuildContext context) {
return ProviderScope(
child: MaterialApp(
localizationsDelegates: context.localizationDelegates,
supportedLocales: context.supportedLocales,
locale: context.locale,
debugShowCheckedModeBanner: false,
// Todo: Implement dark mode color theme
theme: lightTheme,
onGenerateRoute: AppRouter.generateRoutes,
));
}
You can use navigator key to access current context from anywhere. You have to create global key and pass it to material app.
//create key
final navigatorKey = new GlobalKey<NavigatorState>();
//pass it to material app
Widget build(BuildContext context) {
return ProviderScope(
child: MaterialApp(
navigatorKey: navigatorKey, //key
localizationsDelegates: context.localizationDelegates,
supportedLocales: context.supportedLocales,
locale: context.locale,
debugShowCheckedModeBanner: false,
// Todo: Implement dark mode color theme
theme: lightTheme,
onGenerateRoute: AppRouter.generateRoutes,
));
}
//access context
print(navigatorKey.currentContext.locale.toString());
Your service class should be locale independent by design, so taking locale as an input, for example:
class I18nService {
final String locale;
I18nService(this.locale);
String sayHello() {
if (locale == 'en_CA') {
return 'hello Canada';
}
return 'hello world';
}
}
I'd also recommend looking into the provider package, you can instantiate your service using a ProxyProvider and pass in the locale there.

Flutter Cupertino/Material combined Theme / shared font family

I've been trying to solve a couple of problems using both Material and Cupertino widgets.
1) I can't seem to figure out how to globally define a font family for both. Either I use a CupertinoApp and have the Cupertino widgets correct or MaterialApp and have the Material theme widgets correct.
2) How do I define the Cupertino primaryColor attribute without a CupertinoTheme?
I've found something called MaterialBasedCupertinoThemeData but I'm not sure how it works / can't find any docs/tutorials on it.
I can't find any other questions on the subject and would appreciate the help!
#override
Widget build(BuildContext context) {
return CupertinoApp(
title: 'betterfriend',
debugShowCheckedModeBanner: false,
// This lets us use material components
localizationsDelegates: <LocalizationsDelegate<dynamic>>[
DefaultMaterialLocalizations.delegate,
DefaultWidgetsLocalizations.delegate,
DefaultCupertinoLocalizations.delegate,
],
home: CupertinoTheme(
data: MaterialBasedCupertinoThemeData(
materialTheme: ThemeData(textTheme: Styles.textTheme),
),
child: ScreenSwitcher(),
),
);
}