【问题标题】:How do I call async property in Widget build method如何在 Widget 构建方法中调用异步属性
【发布时间】:2019-05-16 23:03:04
【问题描述】:

我是 Flutter 和 Dart 的新手,我正在尝试构建一个在屏幕上显示设备信息的 Flutter 应用程序。为此,我试图从这里使用这个库:'device_info':https://pub.dartlang.org/packages/device_info#-readme-tab-

在 MyApp 类的“build”方法中,我试图从“device_info”包中实例化对象并调用一个恰好是异步属性的属性。由于默认的构建方法不是异步的,那么如何在构建方法中调用这个属性呢?以下是我的代码:

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    DeviceInfoPlugin deviceInfoPlugin = DeviceInfoPlugin();
    AndroidDeviceInfo androidDeviceInfo = await deviceInfoPlugin.androidInfo;
    return MaterialApp(
      title: 'My Device Info',
      home: Scaffold(
        appBar: AppBar(
          title: Text('My Device Info'),
        ),
        body: Center(
          child: Text('Device model:' + 'Moto'),
        ),
      ),
    );
  }
}

【问题讨论】:

    标签: dart flutter


    【解决方案1】:

    我建议你使用FutureBuilder:

    import 'package:flutter/material.dart';
    
    class MyApp extends StatefulWidget {
      @override
      _MyAppState createState() => _MyAppState();
    }
    
    class _MyAppState extends State<MyApp> {
      // save in the state for caching!
      DeviceInfoPlugin _deviceInfoPlugin;
    
      @override
      void initState() {
        super.initState();
        _deviceInfoPlugin = DeviceInfoPlugin();
      }
    
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          title: 'My Device Info',
          home: Scaffold(
            appBar: AppBar(
              title: Text('My Device Info'),
            ),
            body: FutureBuilder<AndroidDeviceInfo>(
              future: _deviceInfoPlugin.androidInfo,
              builder: (BuildContext context, AsyncSnapshot<AndroidDeviceInfo> snapshot) {
                if (!snapshot.hasData) {
                  // while data is loading:
                  return Center(
                    child: CircularProgressIndicator(),
                  );
                } else {
                  // data loaded:
                  final androidDeviceInfo = snapshot.data;
                  return Center(
                    child: Text('Android version: ${androidDeviceInfo.version}'),
                  );
                }
              },
            ),
          ),
        );
      }
    }
    

    一般来说,当使用FutureBuilderFutures 时,您必须记住,封闭的小部件可以随时重建(例如,因为设备被旋转,或者键盘被显示)。这意味着再次调用build 方法。

    在这种特殊情况下,这不是问题,因为插件会缓存该值并立即返回它,但一般来说,您永远不应该在 build 方法中创建或获取 Future。相反,从initState 或单击事件处理程序执行此操作:

    import 'package:flutter/material.dart';
    
    class FooWidget extends StatefulWidget {
      @override
      _FooWidgetState createState() => _FooWidgetState();
    }
    
    class _FooWidgetState extends State<FooWidget> {
      Future<int> _bar;
    
      @override
      void initState() {
        super.initState();
        _bar = doSomeLongRunningCalculation();
      }
    
      void _retry() {
        setState(() {
          _bar = doSomeLongRunningCalculation();
        });
      }
    
      @override
      Widget build(BuildContext context) {
        return Column(
          children: <Widget>[
            FutureBuilder<int>(
              future: _bar,
              builder: (BuildContext context, AsyncSnapshot<int> snapshot) {
                if (snapshot.hasData) {
                  return Text('The answer to everything is ${snapshot.data}');
                } else {
                  return Text('Calculating answer...');
                }
              },
            ),
            RaisedButton(
              onPressed: _retry,
              child: Text('Retry'),
            )
          ],
        );
      }
    }
    
    Future<int> doSomeLongRunningCalculation() async {
      await Future.delayed(Duration(seconds: 5)); // wait 5 sec
      return 42;
    }
    

    【讨论】:

    • 感谢 boformer,我能够使用您建议的代码更改来构建应用程序。但是当我在模拟器上运行应用程序时,${androidDeviceInfo.version} 的值会打印出文本“AndroidBuildVersion 实例”。我在这里有什么遗漏吗?
    • 没关系,我通过访问 AndroidBuildVersion 对象的属性解决了上述问题。
    • 非常感谢。你让我免于凌晨 4 点发疯!
    • 这是一个非常简洁的解决方案。谢谢
    • 这个回复很有帮助,但是,我发现句子 “你永远不应该在构建方法中创建或获取 Future” 非常令人困惑,因为你仍然必须在 FutureBuilder 中获得一个 Future。我认为您想说的是,您不应该在构建方法的return-statement 之上创建 Future,因为即使在 Future 已经完成一次之后,它也会随着 UI 的每次更改而重新创建。对吗?
    【解决方案2】:

    build() 需要同步结果,因此在 build() 中使用 async/await 是不合适的。

    要么使用FutureBuilder,在异步结果尚不可用时返回占位符Container(),要么将异步代码移动到initState(),并在值变为可使用@ 时使用setState 更新状态987654329@再次执行。

    【讨论】:

    • initState() + setState 是 FutureBuilder 的最佳选择,以防父对象需要来自其他构建对象的数据来构建子对象。
    • “好”的是 initState 和 setState 都不能是异步的...
    • 您可以从initState 调用一个异步方法。只需将您尝试执行的代码移动到另一个函数并从initState 调用它。在initState() 中无需await。异步执行完成后,只需调用setState(...)
    • 很好的答案,但是一些源代码示例会使它变得更好:)(对于答案和上面initState的评论)
    • 这里是示例代码:@override void initState() { super.initState(); _audioPlayerService.isSoundTurnedOn.then((value) { setState(() { isSoundSwitched = value; }); }); } 但是,在我看来,使用 FutureBuilder 比在 initState 中处理更好。
    【解决方案3】:

    您可以通过使用 await/async (@Günter Zöchbauer 说不需要按照最新版本的 dart 导入 lib。)

    并从构建方法中调用函数。

      _getAndroidDeviceInfo() async{
        AndroidDeviceInfo androidInfo = await deviceInfo.androidInfo;
        print(androidInfo.device);
      }
    
       _get_iOS_DeviceInfo() async{
        IosDeviceInfo iosDeviceInfo = await deviceInfo.iosInfo;
        print(iosDeviceInfo.model);
      }
    

    【讨论】:

    • 您不必为此导入dart:async。有一些类,如StreamSubscriptionCompleter,......需要导入。在以前的版本中,当您使用 StreamFuture 类型时也需要它,但在最新的 Dart 中,这些类型是从 dart:core 导出的,因此可以自动使用。
    • @GünterZöchbauer - 感谢您的明确。我会更新答案
    • 如果必须返回构建方法怎么办? async 方法应该总是必须返回 Future> 对象
    • 这不是与问题匹配的正确答案。再次查看问题中的问题描述。问题是如何在无状态小部件中调用 aync 函数
    • @boformer 的回答是正确的,即使在这种情况下使用无状态小部件是不可能的。
    猜你喜欢
    • 2018-06-21
    • 2021-03-24
    • 1970-01-01
    • 2017-03-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多