marcglasberg / i18n_extension

Flutter package: Easy and powerful internationalization using Dart extensions.
Other
294 stars 64 forks source link

Where to put the I18n widget in relation to MaterialApp #10

Open q876625596 opened 4 years ago

q876625596 commented 4 years ago

════════ 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. (package:jtw_app_flutter/ui/entertainment/entertainmentHome/shopPublish/shopAddMenu/shop_add_menu.dart:55:16)

3 _InkResponseState._handleTap (package:flutter/src/material/ink_well.dart:706:14)

4 _InkResponseState.build. (package:flutter/src/material/ink_well.dart:789:36)

... Handler: "onTap" Recognizer: TapGestureRecognizer#8c92c debugOwner: GestureDetector state: possible won arena finalPosition: Offset(398.0, 52.8) finalLocalPosition: Offset(33.6, 28.8) button: 1 sent tap down ════════════════════════════════════════════════════════════════════════════════════════════════════

q876625596 commented 4 years ago
  static _I18nState of(BuildContext context) {
    return context.dependOnInheritedWidgetOfExactType<_InheritedI18n>().data;
  }

'context.dependOnInheritedWidgetOfExactType<_InheritedI18n>()' is null

marcglasberg commented 4 years ago

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.

marcglasberg commented 4 years ago

@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").

ManojMM026 commented 4 years ago

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

marcglasberg commented 4 years ago

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.

ManojMM026 commented 4 years ago

I think It will be great if you write sample app which shows how to change locale of entire app using i18n_extension.

marcglasberg commented 4 years ago

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.

EducatedDeer commented 4 years ago

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.

marcglasberg commented 4 years ago

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

EducatedDeer commented 4 years ago

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 { @override Widget build(BuildContext context) { return I18n( child: Column( children: [ 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")), ], ), ); } }

RenanDelfanti commented 4 years ago

I got same error.

marcglasberg commented 4 years ago

@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

RenanDelfanti commented 4 years ago

@marcglasberg In my case, I found that I needed to use the i18n widget above the provider's widget. Solved to my case.

Christian-Martensson commented 4 years ago

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!

crizant commented 4 years ago

I would like to point out a few things here:

  1. The Locale object should be created by Locale('pt', 'br') not Locale('pt_BR')
  2. The I18n.of(context).locale = Locale("pt", "br") statement don't need to wrap inside a setState() call.
  3. If you have a home parameter in the MaterialApp widget, you may put the I18n there, just like the example. But if you handle your routes with a routes map, you may wrap your MaterialApp inside I18n 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.

doc-rj-celltrak commented 4 years ago

@crizant Great clarification in point 3.

QLaille commented 4 years ago

Just in case I would like to confirm that what @Christian-Martensson and @RenanDelfanti suggested works !

manualfromcolumbia commented 4 years ago

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.

crizant commented 4 years ago

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.

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

hlemcke commented 3 years ago

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):

This can be achieved when:

  1. the locale to use (lets call it thisAppsLocale) is determined before building MaterialApp and I18n
  2. thisAppsLocale is used by: Localizations, I18n and also intl
  3. 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);
    }
}
RaulUrtecho commented 2 years ago

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.

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

LaysDragon commented 2 years ago

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. :/

LaysDragon commented 2 years ago

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;
      },
      ...
}
k2s commented 1 year ago

My solution with router (go_router) and provider (riverpod). Should work also with I18n.of(context).locale without using of provider.

    final Locale? appLanguage = ref.watch(appLanguageProvider);

    return MaterialApp.router(
      builder: (context, child) {
        // the child here is the Router and context is already available
        // also the localeListResolutionCallback method was already evaluated
        return I18n(initialLocale: appLanguage, child: child!);
      },
      localizationsDelegates: [
        GlobalMaterialLocalizations.delegate,
        GlobalWidgetsLocalizations.delegate,
        GlobalCupertinoLocalizations.delegate,
//      ...