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.
Related
I want to internationalize my application. To ease translations, I want to use i18n_extension package.
I need to get the list of device locales (any device, even web) before any widget building process starts.
This is required to initially set the correct locale (which can be taken from a server) both for I18n and for Localizations (in WidgetsApp).
The only way I found to get the list of device locales is:
MaterialApp(
localeListResolutionCallback: (deviceLocales, appLocales) => myLocaleResolution(deviceLocales, appLocales),
locale: thisAppsLocale,
home: I18n(
child: MyHomePage(),
initialLocale: thisAppsLocale, ),
Method myLocaleResolution sets thisAppsLocale. Method is invoked once at startup and when the user changes the device locale. So thisAppsLocale is only available after the first build process and cannot be used to set locale and initialLocale. Using setState() within myLocaleResolution throws an exception on startup invocation.
Use window.locales:
import 'dart:ui';
import 'package:flutter/material.dart';
void main(){
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
locale: window.locale,
supportedLocales: window.locales,
home: Scaffold(),
);
}
}
The window singleton gives you access to the Window class.
I am currently learning app development with Flutter and have started learning about the Provider package. I was having some difficulty and was getting the error:
"Could not find the correct Provider above this ... Widget"
I ended up moving the Provider widget to wrap around my MaterialApp widget instead of my Scaffold Widget, and that seemed to fix things.
That being said, I'm not sure why this fixed things. Are we supposed to put our Provider widget around our MaterialApp? If so, can someone please explain why this is needed? If not, can someone explain how to determine where to place the Provider widget in our tree?
Usually, the best place is where you moved it, in the MaterialApp. This is because since that is where the app starts, the node tree will have access to the provider everywhere.
If your page is a Stateful widget - inside Widget wrap State with Provider, so you can use it inside of State. This is a much cleaner solution because you won't have to wrap your entire application.
If you need the functionality of Provider everywhere in the app - yes, wrapping the entire app is completely fine, though I'll prefer to use some kind of service for this
You could add it to any route and pass it to the route you need to use or you can add it to MaterialApp
so you can use it anywhere.
The best practice of using provider:
Place the Provider widget at the top of the widget tree. Bellow I put a template code that can be used for one more providers at the same place, by using MultiProvider widget under Provider package.
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
ProviderName<ModelName>(create: (_) => ModelName()),
AnotherProviderName<AnotherModelName>(create: (_) => AnotherModelName()),
],
child: MaterialApp(
title: 'App title',
theme: ThemeData(
primarySwatch: Colors.blue,
primaryColor: const Color(0xFF2196f3),
accentColor: const Color(0xFF2196f3),
canvasColor: const Color(0xFFfafafa),
),
home: MyHomePage(), // Your widget starting
),
);
}
}
For more informatin: https://pub.dev/documentation/provider/latest/
To manage themes in my MaterialApp(), i use :
MaterialApp(
...
theme: isDarkTheme ? darkTheme() : lightTheme(),
)
darkTheme() and lightTheme() are just simple function that return ThemeData()
So, I use an inherited Widget to change the variable isDarkTheme and then i use the setState((){}) method and all the application is rebuild
But, I saw on the Flutter documentation that we can use darkTheme:.
I'm trying to do this :
MaterialApp(
...
theme: lightTheme(),
darkTheme: darkTheme()
)
And we can change the theme during the course of the application
darkTheme property is used to define a Theme to be used when the device enters Dark mode, So you can't force your app to use darkTheme property because it depends on the value of MediaQueryData.platformBrightness which is a read-only field.
You can rather define several Themes to be used as a value for Theme property, and switching between them during the app course by using a StreamBuilder wrapped around your MaterialApp,which won't cause the app to stop and rebuild like the case when you were using setState to change theme (you'll need rxdart package to apply the below solution):
//BehaviorSubject stream any changes immediately without explicit call
var themeSubject = BehaviorSubject<ThemeData>();
//This way the app won't rebuild when the user selects the same theme
Stream<ThemeData> getTheme() => themeSubject.stream.distinct();
void setTheme (MaterialColor color){
pointsSubject.sink.add(ThemeData(primarySwatch: color) ;
}
return StreamBuilder<ThemeData>(
stream: getTheme(),
initialData: ThemeData(
primarySwatch: Colors.blue,
primaryTextTheme: TextTheme(
title: TextStyle(color: Colors.white),
),
builder: (context, themeSnapshot){
return MaterialApp(theme: themeSnapshot.data);
}
):
Then use your InheritedWidget to access setTheme and change it as you please.
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,
);
},
);
},
),
),
);
}
Need to query server for an object before pushing the ProfileWidget route.
Not sure if error message has anything to do with the Future call inside the MaterialPageRoute's builder (see routes function)?
ERROR
flutter: Another exception was thrown: The builder for route "null" returned null.
..
#override
Widget build(BuildContext context) {
return new MaterialApp(
onGenerateRoute: routes,
debugShowCheckedModeBanner: false,
theme: new ThemeData(
primarySwatch: Colors.blueGrey,
textTheme: TextTheme(
)
),
home: ...,
)
;
}
Route routes(RouteSettings settings) {
if (settings.name.startsWith("/profile")) {
String username = settings.name.replaceFirst("/profile/", '');
return MaterialPageRoute(
builder: (context) {
api.fetchProfile(username)
.then((profile) {
return ProfileWidget(profile);
});
}
);
}
}
Yes, that's exactly where the issue is. Async methods don't work properly within a build function as the build function is expecting a widget to be returned right away - by using a future you're actually just returning null.
Another issue is that you're calling your api in the build function - think about it this way... the build function is called any time anything is changed in your widget. Do you really want to fetch the profile every time that happens?
Instead, I'd recommend using a FutureBuilder and starting the future in initState, or starting the future in initState and using setState(() ...) after completion. Either way, if you do it this way you'll have to deal with the case where there's a brief period of time before the profile is created, unless you think about doing something like moving the profile loading out of the widget into the main function (I don't know if that's really recommended but it seems to work for me).
You might even think about putting the profile into an InheritedWidget or ScopedModel so that if you change the logged-in user it propagates automatically.