【问题标题】:How to make sure UI gets updated in flutter before a long task completes on the main thread如何确保在主线程上完成长任务之前更新 UI
【发布时间】:2018-09-11 20:26:33
【问题描述】:

更新:已解决,请看下面的答案。

我有一个简单的文本(初始文本值为“空”)小部件和屏幕顶部的一个按钮。当我单击按钮时,我正在加载一个需要很长时间才能完成的文件。在完成该任务的同时,我想将 Text 小部件更改为“正在加载”,然后在该任务完成加载文件后显示文件的内容。

我在 Future 和 State 类的构建函数中运行加载任务,我使用 FutureBuilder 小部件根据任务是否完成来了解 Text 小部件应显示的内容。

我认为问题在于,因为 dart 是一个单线程应用程序,将 Text 小部件的文本更改为“正在加载”不会仅在任务完成后执行,这超出了目的(即使当调用 setState,因为它被添加到事件循环中,我相信 Future 也计划在其中运行)。

我应该在单独的 Isolate 中运行长任务吗?这是正确的方法吗?搞这个很久了,希望有人能帮忙,应该就是这么简单..

这是我正在使用的代码:

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

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      debugShowCheckedModeBanner: false,
      title: 'Flutter Demo',
      home: new MyHomePage(),
    );
  }
}

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

  @override
  _MyHomePageState createState() => new _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  String content = 'Empty';
  Future<FileObject> file;

  loadData() async {
     // do the loading of the file here (that takes a LONG time)
file = /*loadfunction for the file (returns a Future, because it takes long)*/;
  }


  loadTask() {
    setState(() {
      content = 'Loading...';
    });
    loadData();
  }

  @override
  Widget build(BuildContext context) {
    return new Scaffold(
      backgroundColor: Colors.black,
      body: Column(
        children: <Widget>[
          buildTitle(),
          buildContent(),
        ],
      ),
    );
  }

  FutureBuilder<FileObject> buildContent() {
    return FutureBuilder<FileObject>(
      future: file,
      builder: (BuildContext context, AsyncSnapshot<FileObject> snapshot) {
        if (snapshot.hasData) {
           content = snapshot.data.text; // The FileObject has a text property
        }

        return Expanded(
          child: SingleChildScrollView(
            child: Text(
              content,
              textAlign: TextAlign.center,
              style: TextStyle(
                color: Colors.lightBlue[100],
                fontSize: 25.0,
                fontWeight: FontWeight.normal,
              ),
            ),
          ),
        );
      },
    );
  }

  Padding buildTitle() {
    return Padding(
      padding: const EdgeInsets.only(left: 8.0, top: 50.0, bottom: 30.0),
      child: Row(
        children: <Widget>[
          IconButton(
            icon: Icon(
              Icons.update,
              color: Colors.lightBlue[50],
            ),
            onPressed: loadTask,
          ),
        ],
      ),
    );
  }
}

【问题讨论】:

  • 你的loadContent函数没用。您可以直接访问 snapshot.data 中的文件值 :)
  • 我实际上只是现在更改了它,并按照您的建议将该 loadContent 活动移动到 FutureBuilder 部分(当然不需要 setState,并使用 if snapshot.hasData 而不是检查连接状态),但是仍然没有改变任何东西......太奇怪了
  • 你能更新你的代码吗?
  • 当然,现在应该更新
  • 为什么不放弃 FutureBuilder 并在文件加载时再次调用 setState?

标签: dart flutter


【解决方案1】:

尝试稍微改变你的构建方法

    buildContent() {
    new FutureBuilder<FileObject>(
      future: loadData(),
      builder: (BuildContext context, AsyncSnapshot<FileObject> snapshot) {
      FileObject file = new FileObject();
        if (snapshot.hasData) {
          file = snapshot.data;
          content = file.text; // The FileObject has a text property
          return Expanded(
          child: SingleChildScrollView(
            child: Text(
              content,
              textAlign: TextAlign.center,
              style: TextStyle(
                color: Colors.lightBlue[100],
                fontSize: 25.0,
                fontWeight: FontWeight.normal,
              ),
            ),
          ),
        );
        } else {
          return new Center(
            child: Column(
              children: <Widget>[
                new LoadingIndicator(),                
              ],
            )
          );
        }
      },
    );
  }

在上面的代码中,我改变了一些东西。

首先,buildContent 方法不是返回方法,因此我删除了 Future 和 return 语句。

其次,future 现在调用 loadData() 方法。快照准备好后将返回您的 FileObject。

最后,Expanded 小部件被移动到 IF 子句中,这样只有在 Futurebuilder 完成后,它才会为您创建一个 FileObject 来显示内容字符串。但是,如果快照为空意味着 Futurebuilder 尚未完成或创建 FileObject 时出错,那么它应该只显示加载文本和微调器(我添加了微调器,因为很高兴知道应用程序没有冻结)。

编辑:

这是我的加载类,我已经编辑了上面调用类的代码

import 'package:flutter/material.dart';

class LoadingIndicator extends StatefulWidget {
  @override
  _LoadingIndicatorState createState() => new _LoadingIndicatorState();
}

class _LoadingIndicatorState extends State<LoadingIndicator>
    with TickerProviderStateMixin {
  Animation<int> animation;
  AnimationController controller;

  Widget childBuilder(int value) {
    String text = 'one moment\n';
    for (int i = 0; i < value; i++) {
      text += '.';
    }

    return new Stack(
      alignment: Alignment(0.0, 0.0),
      children: <Widget>[ 
        // SizedBox(  
        // child: new CircularProgressIndicator(),
        // height:  10.0,
        // width: 400.0,
        // ),
       SizedBox(  
        // height:  40.0,
        child: new Padding(
          padding: EdgeInsets.fromLTRB(0.0, 0.0, 0.0, 30.0),
          child: new Text(text,style: new TextStyle(color: Colors.blueGrey, fontSize: 20.0, fontWeight: FontWeight.w500,), textAlign: TextAlign.center,),
        )
       )
      ]
    );


  }

  @override
  void initState() {
    super.initState();
    controller = new AnimationController(
        vsync: this, duration: const Duration(seconds: 2));
    animation = new StepTween(begin: 0, end: 4).animate(controller)
    .. addStatusListener((status){
      if (status == AnimationStatus.completed) {
        controller.reset();
        controller.forward();
      }
    });
    controller.forward();
  }

  @override
  Widget build(BuildContext context) {
    return new AnimatedBuilder(
        animation: animation,
        builder: (BuildContext context, _) {
          return childBuilder(animation.value);
        });
  }

  @override
  void dispose() {
    controller.dispose();
    super.dispose();
  }
}

【讨论】:

  • 谢谢大家的回复!不幸的是,这仍然行不通。在执行 Future 并执行 setState 之后(让构建函数再次执行,因此 FutureBuilder 将工作并返回加载动画,直到 Future 实际完成),构建函数不会再次被调用,直到 Future 结束(所以加载动画永远不会出现)。同样,我认为 dart 是一个单线程应用程序,因此 Future 的实际执行放在 setState 之前的事件循环中,并且只有在 Future 完成后才会调用 build。
  • 我用它打开了一个我根本没有设置状态的 epub 文件,在解码 epub 时加载文本会显示 5 秒左右。
  • 它将显示“正在加载”文本,但 CircularProgressIndicator 的动画将一直卡住,直到大任务完成。我在某处看到,对于大型任务,如果您不希望 UI 卡住,建议使用“计算”​​功能,但这也不完美(您需要向主隔离报告计算功能已完成并显示内容,但显然你不能使用全局变量来传达它,因为不同的 Isolates 有自己的副本)。认为这应该是一个简单的用例(在动画在后台运行时执行一项大任务)。
  • 我将不得不研究计算功能。我将与您分享我在项目中使用的加载类。
  • 谢谢,这很有帮助,也证实了我的想法。我在研究该主题时也遇到了 AsyncLoader,并认为它可以解决问题,但同样,由于您提供的文章中所写的内容,没有任何进展。所以我想说让它工作的唯一方法是在隔离中运行它并将完成后加载的数据发送回主线程。
【解决方案2】:

我终于解决了它,使用上面线程中讨论的 Isolates。

过程很简单,我这里按照例子:Isolate Example

由于长任务现在在与主任务不同的隔离/线程中运行,因此加载动画按预期工作,直到来自隔离的回调被触发并显示加载的内容。

感谢大家的帮助。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-04-08
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多