如何在不重新加载每个构建的情况下从构建上下文加载 Flutter 文本资产?

How can I load Flutter text assets from a build context without reloading each build?

我对 loading text assets 上的 Flutter 文档感到困惑。

它指出:

Each Flutter app has a rootBundle object for easy access to the main asset bundle. It is possible to load assets directly using the rootBundle global static from package:flutter/services.dart.

However, it’s recommended to obtain the AssetBundle for the current BuildContext using DefaultAssetBundle, rather than the default asset bundle that was built with the app; this approach enables a parent widget to substitute a different AssetBundle at run time, which can be useful for localization or testing scenarios.

我不明白如何以推荐的方式实现文本资产加载,同时避免每次构建小部件时都加载资产。

考虑以下使用 FutureBuilder 显示一些文本的简单示例:

@override
Widget build(BuildContext context) {
  var greeting = DefaultAssetBundle.of(context).loadString('assets/greeting');
  return FutureBuilder<String>(
    future: greeting,
    builder (BuildContext context, AsyncSnapshot<String> snapshot) {
      if (snapshot.hasData) {
        return Text(snapshot.requireData);
      } else {
        return Text('Loading greeting...');
      }
    }
  );
}

在示例中,每当构建小部件时都会调用 loadString。这对我来说似乎效率低下。

此外,它明确违反了 FutureBuilder 文档,该文档告诉我:

The future must have been obtained earlier, e.g. during State.initState, State.didUpdateWidget, or State.didChangeDependencies. It must not be created during the State.build or StatelessWidget.build method call when constructing the FutureBuilder. If the future is created at the same time as the FutureBuilder, then every time the FutureBuilder's parent is rebuilt, the asynchronous task will be restarted.

现在,我可以继续使用任何推荐的方法加载我的资源,但其中 none 个 BuildContext。这意味着我必须使用不推荐的 rootBundle

由于我是 Flutter 的新手,我不确定文档是否自相矛盾,或者我是否遗漏了一些明显的东西。任何澄清将不胜感激。

我想对你说...删除 Future 构建器。 你可以这样做:

  String greeting;

  @override
  void initState() {
    super.initState();
    WidgetsBinding.instance.addPostFrameCallback((_) async {
      greeting =
          await DefaultAssetBundle.of(context).loadString('assets/greeting');
      setState(() {});
    });
  }

  @override
  Widget build(BuildContext context) {
    if (greeting != null && greeting.isNotEmpty) {
      return Text(greeting);
    } else {
      return Text('Loading greeting...');
    }
  }

试试这个

String greeting;

@override
void initState() {
    super.initState();
    WidgetsBinding.instance.addPostFrameCallback((_) => loadString());
}

void loadString() async {
    if (greeting != null && greeting.isNotEmpty) {
        greeting = await DefaultAssetBundle.of(context).loadString('assets/greeting');
        setState(() {});
    }
}

@override
Widget build(BuildContext context) {
    return Text(greeting ?? 'Loading greeting...');
}

如果您的项目有 Null Safety,则将 String greeting 更改为 String? greeting

我想出了以下解决方案,用于按照 Flutter 文档中推荐的方式加载资产。

在 Widget 的 State 中加载资产,并将 Future 分配给可为 null 的实例变量。这与 FutureBuilder 结合使用。这是一个例子:

class MyWidgetState extends State<MyWidget> {

  Future<String>? _greeting;

  @override
  Widget build(BuildContext context) {
    // Wrapping `loadString` in a condition such as the following ensures that
    // the asset is loaded no more than once. Seems kinda crude, though.
    if (_greeting == null) {
      _greeting = DefaultAssetBundle.of(context).loadString('assets/greeting');
    }

    return FutureBuilder<String>(
        future: greeting,
        builder: (BuildContext context, AsyncSnapshot<String> snapshot) {
          if (snapshot.hasData) {
            return Text(snapshot.requireData);
          } else {
            return Text('Loading greeting...');
          }
        });
  }
}

这种方法确保 Flutter 文档中的两个建议都得到尊重:

  • 资源是从 Bundle 为当前 BuildContext 加载的。
  • 资产只加载一次,避免FutureBuilder重新启动每个构建。