i18n_extension
i18n_extension copied to clipboard
Where to put the I18n widget in relation to MaterialApp
════════ Exception caught by gesture ═══════════════════════════════════════════════════════════════ The following NoSuchMethodError was thrown while handling a gesture: The getter 'data' was called on null. Receiver: null Tried calling: data
When the exception was thrown, this was the stack:
#0 Object.noSuchMethod (dart:core-patch/object_patch.dart:53:5)
#1 I18n.of (package:i18n_extension/i18n_widget.dart:74:73)
#2 ShopAddMenu.build.
static _I18nState of(BuildContext context) {
return context.dependOnInheritedWidgetOfExactType<_InheritedI18n>().data;
}
'context.dependOnInheritedWidgetOfExactType<_InheritedI18n>()' is null
Can you please provide me with some minimum runnable code (with main method and all) demonstrating the problem? I need to reproduce it to be able to fix it.
@q876625596 Could you please upgrade to version 1.3.0 and tell me if it solved your problem?
Please note, in this new version you cannot do Locale("pt_BR") anymore, since this is actually the wrong way to create a locale. You must do Locale("pt", "BR").
This issue is still there in 1.3.0, also How do we change locale of entire flutter app? I am trying to change locale from my settings page
Ok. You still need to provide me with some minimum runnable code (with main method and all) demonstrating the problem. I need to reproduce it to be able to fix it.
I think It will be great if you write sample app which shows how to change locale of entire app using i18n_extension.
You already have this sample app in the included example
directory:
https://github.com/marcglasberg/i18n_extension/tree/master/example/lib
You must run main.dart
there.
As i see it, it(manually changing languages) only works on the main page. When i make a Settings page or When i make a alertdialog to change to different languages it gives the same error above. Example app only has 1 screen.
Can you please provide me with some minimum runnable code (with main method and all) demonstrating the problem? I need to reproduce it to be able to fix it.
Please follow these guidelines: https://stackoverflow.com/help/minimal-reproducible-example
Same code as Example: just added a button to my_screen.dart and a change language page
RaisedButton( child: Text("Language Test"), onPressed:(){ Navigator.push(context, MaterialPageRoute(builder: (context) => ChangeLang())); } ,),
Change Language page
import 'package:flutter/material.dart'; import 'package:i18n_extension/i18n_widget.dart';
class ChangeLang extends StatefulWidget { @override _ChangeLangState createState() => _ChangeLangState(); }
class _ChangeLangState extends State<ChangeLang> { @override Widget build(BuildContext context) { return I18n( child: Column( children: <Widget>[ RaisedButton(onPressed: (){ setState(() {I18n.of(context).locale = Locale("pt", "BR");}); },child: Text("PT")), RaisedButton(onPressed: (){ setState(() {I18n.of(context).locale = Locale("en", "US");}); },child: Text("EN")), ], ), ); } }
I got same error.
@EducatedDeer Please post here a complete example code, as simple as possible to demonstrate the problem, for a single file including a main method.
Please follow these guidelines: https://stackoverflow.com/help/minimal-reproducible-example
@marcglasberg In my case, I found that I needed to use the i18n widget above the provider's widget. Solved to my case.
Hi! I'd like to add to this issue, as I spent hours struggling with it. What @RenanDelfanti said works. Instead of placing the I18n wrapper widget after the MaterialApp widget, I put it as the first widget in the widget tree. This solved the problem for me. I see the problem was not thoroughly explained in this thread. For me it was like this:
I have a preferences page which can be navigated to through a drawer and Navigator.push() method. While this pref page was in the drawer, and I navigated to it and changed language, I would get the same error:
I18n.of(context) NoSuchMethodError: The getter 'data' was called on null.
However, if I put the preferences page directly into the home page of the app (the page that opens at start up), the language switching works flawlessly. By moving the I18n wrapper widget to the top of the app, the problem was fixed.
I18n(child: MaterialApp())
It would be great if you could update the documentation and example app to reflect this change, i.e. so that the I18n widget is moved further up the widget tree. Thank you kindly for this amazing package, it is much better than the current alternatives for flutter!
I would like to point out a few things here:
- The
Locale
object should be created byLocale('pt', 'br')
notLocale('pt_BR')
- The
I18n.of(context).locale = Locale("pt", "br")
statement don't need to wrap inside asetState()
call. - If you have a
home
parameter in theMaterialApp
widget, you may put theI18n
there, just like the example. But if you handle your routes with aroutes
map, you may wrap yourMaterialApp
insideI18n
like:
class MyApp extends StatelessWidget {
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return I18n(
child: MaterialApp(
routes: routes,
initialRoute: '/',
),
),
);
}
}
Because if you don't do this, when you navigate to another route, the I18n
reference will not be kept inside the BuildContext
, and error will be thrown.
@crizant Great clarification in point 3.
Just in case I would like to confirm that what @Christian-Martensson and @RenanDelfanti suggested works !
Just wanted to know if anyone has any issues when wrapping the MaterialApp
inside I18n
(when handling routes).
When I run the example where the I18n
is wrapping a child in the home
parameter, and the iOS simulator has Spanish as the default language, it works.
But when the I18n
wraps the MaterialApp
the default locale is en_us. Is ignoring the current system locale.
Just wanted to know if anyone has any issues when wrapping the
MaterialApp
insideI18n
(when handling routes).When I run the example where the
I18n
is wrapping a child in thehome
parameter, and the iOS simulator has Spanish as the default language, it works.But when the
I18n
wraps theMaterialApp
the default locale is en_us. Is ignoring the current system locale.
Yes, that's why I have implemented a splash screen to detect the language:
class SplashScreen extends StatefulWidget {
@override
_SplashScreenState createState() => _SplashScreenState();
}
class _SplashScreenState extends State<SplashScreen>
with AfterLayoutMixin<SplashScreen> {
@override
void afterFirstLayout(BuildContext context) async {
await _detectLocale();
Navigator.of(context).pushReplacementNamed('/login');
}
void _detectLocale() async {
// default locale of the app
Locale selectedLocale = Locale('en');
// locale of the device
final Locale deviceLocale = Localizations.localeOf(context);
// get stored locale (from local storage or anything if you want),
// check if it is valid;
// if not, see if device locale is supported by the app;
// just find a proper locale and assign it to `selectedLocale`
// ...
// finally, set the selected locale of the app
I18n.of(context).locale = selectedLocale;
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Text('Loading'),
),
);
}
}
p.s.: To have the afterFirstLayout
method, you need to use this package: after_layout
The best approach would be that I18n
uses a given locale with a fallback to use the best matching device locale. This requires I18n
to also accept supportedLocales
as MaterialApp
does.
Example (shortened to use only language code):
- device locales =
[nl, de, en]
- supported locales =
[en, de]
- I18n should resolve to
de
(noten
and for sure notnl
) unless the user has chosende
in some app internal preferences
This can be achieved when:
- the locale to use (lets call it
thisAppsLocale
) is determined before buildingMaterialApp
andI18n
-
thisAppsLocale
is used by:Localizations, I18n
and alsointl
-
thisAppsLocale
can be updated by some app internal selection (e.g. DropdownButton) and also when the user changes the list of device locales
Possible solution:
class MyApp extends StatefulWidget {
MyAppState createState() => MyAppState();
}
class MyAppState extends State<MyApp> {
final List<Locale> supportedLocales = [
Locale('en', 'US'),
Locale('de', 'DE'),
];
Locale thisAppsLocale;
void initState() {
thisAppsLocale = resolveLocale(null);
super.initState();
}
Widget build( BuildContext context) {
Intl.defaultLocale = thisAppsLocale.toString();
return MaterialApp(
home: I18n(
child: MyHomePage(),
locale: thisAppsLocale, // see enhancement request https://github.com/marcglasberg/i18n_extension/issues/55
useLanguageOnly: true, ), // see enhancement request https://github.com/marcglasberg/i18n_extension/issues/54
locale: thisAppsLocale,
localeListResolutionCallback: (_, __) => localeResolutionCallback(),
localizationsDelegates: [
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
],
supportedLocales: DjarjoLocale.supportedLocales, ); }
/// Resolves to use best matching locale.
/// TODO enhance method to try language codes if no full match found
Locale resolveLocale( Locale newLocale ) {
List<Locale> deviceLocales = WidgetsBinding.instance.window.locales; // always use most current list
if ( supportedLocales.contains( newLocale)) { return newLocale; }
for ( Locale devLoc in deviceLocales ) {
if ( supportedLocales.contains( devLoc )) {return devLoc; }
}
return supportedLocales[0]; // Fallback if nothing found
}
Locale localeResolutionCallback() {
Locale newLoc = resolve( null );
setThisAppsLocale( newLoc );
return newLoc;
}
/// Setter method
void setThisAppsLocale( Locale newLocale ) {
Locale locToSet = resolveLocale(newLocale);
if (thisAppsLocale != locToSet) {
setState(() => thisAppsLocale = locToSet);
}
}
Just wanted to know if anyone has any issues when wrapping the
MaterialApp
insideI18n
(when handling routes). When I run the example where theI18n
is wrapping a child in thehome
parameter, and the iOS simulator has Spanish as the default language, it works. But when theI18n
wraps theMaterialApp
the default locale is en_us. Is ignoring the current system locale.Yes, that's why I have implemented a splash screen to detect the language:
class SplashScreen extends StatefulWidget { @override _SplashScreenState createState() => _SplashScreenState(); } class _SplashScreenState extends State<SplashScreen> with AfterLayoutMixin<SplashScreen> { @override void afterFirstLayout(BuildContext context) async { await _detectLocale(); Navigator.of(context).pushReplacementNamed('/login'); } void _detectLocale() async { // default locale of the app Locale selectedLocale = Locale('en'); // locale of the device final Locale deviceLocale = Localizations.localeOf(context); // get stored locale (from local storage or anything if you want), // check if it is valid; // if not, see if device locale is supported by the app; // just find a proper locale and assign it to `selectedLocale` // ... // finally, set the selected locale of the app I18n.of(context).locale = selectedLocale; } @override Widget build(BuildContext context) { return Scaffold( body: Center( child: Text('Loading'), ), ); } }
p.s.: To have the
afterFirstLayout
method, you need to use this package:after_layout
Guys this is not the best we need the i18n package detect this system change
yeah...We really need a good way to integrate I18nWidget with MaterialApp route,or there just a big hole of detect system language here. There is no document mentioned with this problem(which simply make the feature of system language detecting broken) and developing flutter App without route is unrealistic. :/
I found another way to implemented system language detecting without putting it inside any screen widget,by insert logic into the localeListResolutionCallback of Material. Its seems not the best but atleast its works for me.
return MaterialApp(
localeListResolutionCallback: (locales, supportedLocales) {
// get locale from flutter's default locale resolution function
var locale = basicLocaleListResolution(locales, supportedLocales);
// overwrite default locale
I18n.defaultLocale = locale;
// and this one triggered rebuilding
Future.microtask(() => I18n.of(context).locale = locale);
return locale;
},
...
}