【问题标题】:Flutter - Using GetIt with BuildContextFlutter - 将 GetIt 与 BuildContext 一起使用
【发布时间】:2020-12-19 14:20:55
【问题描述】:

我在我的应用程序中使用基于颤振文档的本地化。
请参见此处:https://flutter.dev/docs/development/accessibility-and-localization/internationalization

我使用 get_it 包(4.0.4 版)来检索像本地化委托这样的单例对象。不幸的是,它需要一个BuildContext 属性。有时在我的应用程序中我没有上下文引用,所以如果它像这样工作会很好:GetIt.I<AppLocalizations>() 而不是这个:AppLocalizations.of(context)。如果您像这样设置 get_it,它仍然可以毫无问题地实现:GetIt.I.registerLazySingleton(() => AppLocalizations.of(context)); 问题是您至少需要一次上下文才能使其工作。此外,如果您想在初始路径中立即显示本地化文本,则在需要时获得正确初始化的BuildContext 会更加困难。

我很难正确解释它,所以我在一个最小的例子中重新创建了这个问题。

我注释掉了一些会导致编译时错误的代码,但它显示了我想象中的完成方式。

ma​​in.dart

GetIt getIt = GetIt.instance;

void setupGetIt() {
  // How to get BuildContext properly if no context is available yet?
  // Compile time error.
  // getIt.registerLazySingleton(() => AppLocalizations.of(context));
}

void main() {
  setupGetIt();

  runApp(MyApp());
}

class MyApp extends StatefulWidget {
  MyApp({Key key}) : super(key: key);

  @override
  _MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  @override
  Widget build(BuildContext context) {
    // The above line also won't work. It has BuildContext but Applocalizations.of(context) won't work
    // because it's above in the Widget tree and not yet setted up.
    getIt.registerLazySingleton(() => AppLocalizations.of(context));
    return MaterialApp(
      supportedLocales: const [
        Locale('en', 'US'),
        Locale('hu', 'HU'),
      ],
      localizationsDelegates: const [
        AppLocalizations.delegate,
        GlobalMaterialLocalizations.delegate,
        GlobalWidgetsLocalizations.delegate,
        GlobalCupertinoLocalizations.delegate,
      ],
      localeResolutionCallback: (locale, supportedLocales) {
        // check if locale is supported
        for (final supportedLocale in supportedLocales) {
          if (supportedLocale.languageCode == locale?.languageCode &&
              supportedLocale.countryCode == locale?.countryCode) {
            return supportedLocale;
          }
        }
        // if locale is not supported then return the first (default) one
        return supportedLocales.first;
      },
      // You may pass the BuildContext here for Page1 in it's constructor 
      // but in a more advanced routing case it's not a maintanable solution.
      home: Page1(),
    );
  }
}

初始路线

class PageBase extends StatelessWidget {
  final String title;
  final Widget content;

  PageBase(this.title, this.content);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(title),
      ),
      body: content,
    );
  }
}

class Page1 extends PageBase {
  // It won't run because I need the context but clearly I don't have it.
  // And in a real app you also don't want to pass the context all over the place 
     if you have many routes to manage.
  Page1(String title)
      : super(AppLocalizations.of(context).title, Center(child: Text('Hello')));

  // Intended solution
  // I don't know how to properly initialize getIt AppLocalizations singleton by the time
  // it tries to retrieve it
  Page1.withGetIt(String title)
      : super(getIt<AppLocalizations>().title, Center(child: Text('Hello')));
}

locales.dart

String globalLocaleName;

class AppLocalizations {
  //AppLocalizations(this.localeName);

  static AppLocalizations of(BuildContext context) {
    return Localizations.of<AppLocalizations>(context, AppLocalizations);
  }

  static const LocalizationsDelegate<AppLocalizations> delegate =
      _AppLocalizationsDelegate();

  static Future<AppLocalizations> load(Locale locale) async {
    final String name =
        locale.countryCode.isEmpty ? locale.languageCode : locale.toString();

    final String localeName = Intl.canonicalizedLocale(name);

    return initializeMessages(localeName).then((_) {
      globalLocaleName = localeName;
      return AppLocalizations();
    });
  }

  String get title => Intl.message(
        'This is the title.',
        name: 'title',
      );
}

class _AppLocalizationsDelegate
    extends LocalizationsDelegate<AppLocalizations> {
  // This delegate instance will never change (it doesn't even have fields!)
  // It can provide a constant constructor.
  const _AppLocalizationsDelegate();

  @override
  bool isSupported(Locale locale) {
    return ['en', 'hu'].contains(locale.languageCode);
  }

  @override
  Future<AppLocalizations> load(Locale locale) => AppLocalizations.load(locale);

  @override
  bool shouldReload(_AppLocalizationsDelegate old) => false;
}

还有一些 intl 生成的 dart 代码和 .arb 文件对于说明问题并不那么重要。


总而言之,在这种情况下,如何在不使用上下文的情况下将我的 AppLocalizations 类用作单例?也许我最初的方法很糟糕,可以通过我所代表的其他方式来完成。如果您有解决方案,请告诉我。

谢谢。

【问题讨论】:

    标签: flutter service-locator


    【解决方案1】:

    要实现您所描述的,您需要首先使用get_it 制作导航服务。按照以下步骤来获得结果:

    1。创建导航服务

    import 'package:flutter/material.dart';
    
    class NavigationService {
      final GlobalKey<NavigatorState> navigatorKey =
          new GlobalKey<NavigatorState>();
    
      Future<dynamic> navigateTo(String routeName) {
        return navigatorKey.currentState!
            .push(routeName);
      }
    
      goBack() {
        return navigatorKey.currentState!.pop();
      }
    }
    
    

    这使您可以在整个应用程序中的任何点进行导航,而无需构建上下文。您可以使用此导航键来实现当前上下文的 AppLocalization 实例。 请参阅 FilledStacks 教程,了解这种无需构建上下文的导航方法。

    https://www.filledstacks.com/post/navigate-without-build-context-in-flutter-using-a-navigation-service/

    2。注册

    GetIt locator = GetIt.instance;
    
    void setupLocator() {
      ...
      locator.registerLazySingleton(() => NavigationService());
      ...
    }
    
    

    3。在材质应用中分配导航键

    return MaterialApp(
        ...
        navigatorKey: navigationService.navigatorKey,
        ...
      ),
    

    3。为AppLocalizations 创建一个实例并将其导入到任何你想使用的地方

    localeInstance() => AppLocalizations.of(locator<NavigationService>().navigatorKey.currentContext!)!;
    

    3。实际用例

    import 'package:{your_app_name}/{location_to_this_instace}/{file_name}.dart';
    
    localeInstance().your_localization_variable
    

    【讨论】:

      【解决方案2】:

      您可以将构建器添加到您的 MaterialApp 并在其中设置服务定位器并使用可用的上下文。示例:

        Widget build(BuildContext context) {
          return MaterialApp(
                builder: (context, widget) {
                  setUpServiceLocator(context);
                  return FutureBuilder(
                      future: getIt.allReady(),
                      builder: (BuildContext context, AsyncSnapshot snapshot) {
                        if (snapshot.hasData) {
                          return widget;
                        } else {
                          return Container(color: Colors.white);
                        }
                      });
                },
              );
      }
      

      服务定位器设置:

      void setUpServiceLocator(BuildContext context) {
        getIt.registerSingleton<AppLocalizations>(AppLocalizations.of(context));
      }
      

      【讨论】:

        【解决方案3】:

        您可以使用带有FutureBuildergetIt.allReady() 的一些不可本地化的启动画面。

        类似:

          class SplashScreen extends StatelessWidget {
            @override
            Widget build(BuildContext context) {
              return FutureBuilder<void>(
                future: getIt.allReady(),
                builder: (context, snapshot) {
                  if (snapshot.hasData) {
                    // Navigate to main page (with replace)
                  } else if (snapshot.hasError) {
                    // Error handling
                  } else {
                    // Some pretty loading indicator
                  }
                },
              );
            }
        

        我想推荐 injectable 包来处理 get_it。

        【讨论】:

          猜你喜欢
          • 2018-01-17
          • 1970-01-01
          • 1970-01-01
          • 2018-01-13
          • 2020-03-09
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2021-05-16
          相关资源
          最近更新 更多