有没有办法从 Flutter 中的 Future 函数获取反馈

Posted

技术标签:

【中文标题】有没有办法从 Flutter 中的 Future 函数获取反馈【英文标题】:Is there a way of getting feedback from a Future function in Flutter 【发布时间】:2019-09-11 05:18:02 【问题描述】:

我有这个 Future 函数,它需要很长时间才能加载,它的工作原理基本上类似于视频渲染(它执行某些步骤 100 次)。 有没有办法可以在小部件中显示进度? 我尝试在 MyApp() 中设置一个全局变量,例如对应于框架的 int 并通过setState() 我尝试重建小部件,但它不起作用,应用程序冻结并且小部件没有得到更新。

这是函数:

int _progress = 0;

Future _cycleGame() async 
    await game.cycle((int value) 
      print(value);


      setState(() 
        _progress = value;
      );

    ).whenComplete(() 

      setState(() );
    );
  

game.cycle() 是循环 50 次的 Future 函数。

game.cycle() 函数供参考:

Future cycle(Function cBack) async 

    for (int i in Range(50)) 
      if (!shouldFinish) 

        cBack(i);
        ///Does things.

到目前为止,我只是在屏幕上使用一个文本到 _progress 值。 vlaue 正确打印(每次打印一个),但显示 _progress 的文本仅在函数结束时更新。我做错了什么?

我尝试用谷歌搜索如何做到这一点,但我一无所获。这可能吗?

编辑 1。 使用 Rémi Rousselet 提供的答案,我仍然无法让代码工作。小部件仅在流结束时更新。

这是代码。

    import 'dart:io';

import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget 
  @override
  Widget build(BuildContext context) 
    return MaterialApp(

      home: Foo(),
    );
  



class Foo extends StatefulWidget 
  @override
  _FooState createState() => _FooState();


class _FooState extends State<Foo> 
  Stream<int> _cycleStream;

  @override
  void initState() 
    super.initState();
    _cycleStream = cycle();


  

  Stream<int> cycle() async* 
    for (int i = 0; i <= 50; i++) 
      sleep(Duration(milliseconds: 100));



      yield i;

      this.setState(() 

      );

      // does things
    
  

  @override
  Widget build(BuildContext context) 
    return StreamBuilder<int>(

      stream: _cycleStream,
      initialData: 0,


      builder: (context, snapshot) 

        return Center(
          child: Text(snapshot.data.toString())
        );
      ,
    );
  

颤振医生

[√] Flutter (Channel beta, v1.5.4-hotfix.2, on Microsoft Windows [Versione 10.0.17134.706], locale it-IT) • Flutter version 1.5.4-hotfix.2 at • Framework revision 7a4c33425d (8 days ago), 2019-04-29 11:05:24 -0700 • Engine revision 52c7a1e849 • Dart version 2.3.0 (build 2.3.0-dev.0.5 a1668566e5)

【问题讨论】:

你可以让那个长时间运行的函数接受一个回调,它可以定期调用它来提供进度信息。然后,调用者可以提供一个回调,使用报告的进度调用 setState 当您有一组要观察的事件(中间步骤)时,请考虑使用 Stream 而不是 Future。 @jamesdlin 我尝试将一个函数传递给这种类型为(value) _progress = value; setState(() );. 的长期运行函数,但布局只会在函数结束时更新。 @Luksprog 关于如何做到这一点的任何线索? @fabriziog 您的长时间运行的函数可能不会产生(例如通过使用await)。 【参考方案1】:

您可能需要Stream,而不是Future

Future 仅在它们完全完成时才发出一个值。因此,我们不能使用它们来表示随时间变化的值(这里是进度指示器)。

另一方面,Stream 可以发射无限次。

这是您的 cycle 函数的实现,每次更新时都会发出当前进度:

Stream<double> cycle() async* 
  for (int i in Range(50)) 
    yield i / 50;
    // TODO: does things
  

然后,您可以使用 StreamBuilderdouble 发送到 CircularProgressIndicator

这是一个完整的例子:

class Foo extends StatefulWidget 
  @override
  _FooState createState() => _FooState();


class _FooState extends State<Foo> 
  Stream<double> _cycleStream;

  @override
  void initState() 
    super.initState();
    _cycleStream = cycle();
  

  Stream<double> cycle() async* 
    for (int i = 0; i <= 50; i++) 
      yield i / 50;
      // does things
    
  

  @override
  Widget build(BuildContext context) 
    return StreamBuilder<double>(
      stream: _cycleStream,
      initialData: 0,
      builder: (context, snapshot) 
        return CircularProgressIndicator(
          value: snapshot.data,
        );
      ,
    );
  

【讨论】:

感谢您的回答!但我仍然无法让它工作。我复制了您答案中的文本,并尝试在循环中的产量之前添加sleep(Duration(milliseconds: 100)); 以模拟我应该使用的功能,但只要我运行应用程序,圆圈仍然会完全加载。我错过了什么吗?并且为了调试,我在循环中添加了一个 print(i) 并且每 100 毫秒打印一个数字,但循环仅在 50.0 时更新 它基本上只是在第一次(0.0)和最后一次(1.0)更新 你有没有试过在上面的例子中只使用yield i而不是yield i/50。它将产生 i 的实际值,而不是除商。 我们需要更多关于“没用”的信息。不看你在做什么就很难猜到。 我为当前无法运行的应用添加了代码和颤振医生【参考方案2】:

您尝试过 Future Builder 吗?

Future < Widget > buildAfter() async 
  return Future.delayed(Duration(seconds: 3), () => Text('Hello World!'));


return Scaffold(
  body: FutureBuilder(
    future: buildAfter(),
    builder: (context, snapshot) 
      if (snapshot.hasData) 
        return ListView.builder(
          itemCount: 10,
          itemBuilder: (context, index) => ListTile(
            title: snapshot.data,
            subtitle: Text('Counter: $index'),
          ),
        );
      
      return Center(
        child: CircularProgressIndicator(),
      );
    ,
  ));

在您运行此代码时,它将显示 CircularProgressIndiacator 3 秒,因为我们已经告诉编译器在 Future 函数中保持 3 秒。

当 3 秒结束时,它返回数据,FutureBuilder 收到通知,通知它拥有数据并使用可用的新数据重建小部件。

注意:Future 函数不必返回必要的数据,它只需要返回除错误之外的其他内容,以便侦听的 Builder 函数可以了解何时应该重建自身。

希望您也可以在任何小部件中使用 Future Builder。

如果你想多次发出数据,你可以使用带有 bool 的 FutureBuilder 并检查它是否正在加载,然后显示这个 else 显示那个。而不是 Stream/Streambuilder 并让事情变得不那么复杂。

bool isLoading = false;

Future < Widget > buildAfter() async 
  return Future.delayed(Duration(seconds: 3), () => Text('Hello World!'));


return Scaffold(
  appBar: AppBar(
    elevation: 0.0,
    backgroundColor: greenColor,
    title: const Text('Inbox'),
      actions: < Widget > [
        new IconButton(
          icon: new Icon(Icons.edit),
          onPressed: () 
            setState(() 
              isLoading = !isLoading;
            );
          ),
      ],
  ),
  body: isLoading ?
  Center(
    child: CircularProgressIndicator(
      backgroundColor: Colors.redAccent,
    ),
  ) :
  FutureBuilder(
    future: buildAfter(),
    builder: (context, snapshot) 
      if (snapshot.hasData) 
        return ListView.builder(
          itemCount: 10,
          itemBuilder: (context, index) => ListTile(
            title: Text('Code'),
            subtitle: Text('Counter: $name'),
          ),
        );
      
      return Center(
        child: CircularProgressIndicator(),
      );
    ,
  ));

这段代码中发生的事情是它检查 bool isLoading

如果 isLoading == true => 显示循环进度指示器

如果 isLoading == false => 显示未来的构建器

所以当你必须多次调用它时,你可以将 bool isLoading 的状态设置为 true。然后在计算结束后,您可以将 bool isLoading 的状态设置为 false。

但这取决于用例,当您随时有传入事件时,流总是更好,但是如果您知道自己在做什么,否则流会使自己变得难以理解

【讨论】:

【参考方案3】:

据你所说,你无法让代码工作。

我已经尝试过 Remo 的代码,并且运行良好。

我认为问题在于您的 cycle() 需要 await 来模拟一些延迟。

Stream<double> cycle() async* 
    for (int i = 0; i <= 50; i++) 
      yield i / 50;

      // does things
      await Future.delayed(Duration(seconds: 1));
    
  

【讨论】:

以上是关于有没有办法从 Flutter 中的 Future 函数获取反馈的主要内容,如果未能解决你的问题,请参考以下文章

将 Future 类型化结果从函数分配给 Flutter Widget 中的变量

Flutter 中的 Stream 和 Future

从 SMB 服务器读取/写入 Flutter 应用程序

Flutter:从 Future 中获取价值的正确方法

Flutter - 在future函数之后从getter中获取值

Flutter进阶篇(4)-- Flutter的Future异步详解