在构建异常期间调用的 setState() 或 markNeedsBuild() 阻止我执行回调

Posted

技术标签:

【中文标题】在构建异常期间调用的 setState() 或 markNeedsBuild() 阻止我执行回调【英文标题】:setState() or markNeedsBuild() called during build exception prevents me from executing callback 【发布时间】:2019-10-16 16:28:15 【问题描述】:

一旦计时器在特定小部件中结束,我正在尝试执行回调函数,但我不断收到此异常:

I/flutter (16413):引发了另一个异常:在构建期间调用了 setState() 或 markNeedsBuild()。

所以我有一个名为 countdown 的小部件:

class Countdown extends StatefulWidget 
  final VoidCallback onCountdownExpire;

  Countdown(this.onCountdownExpire);

  @override
  CountdownState createState() => CountdownState();


class CountdownState extends State<Countdown> with TickerProviderStateMixin 
  AnimationController controller;

  String get timerString 
    Duration duration = controller.duration * controller.value;
    return '$duration.inMinutes:$(duration.inSeconds % 60).toString()';
  

  @override
  void initState() 
    super.initState();
    controller = AnimationController(
      vsync: this,
      duration: Duration(seconds: 2),
    )..addStatusListener((AnimationStatus status)
        if (status == AnimationStatus.completed)
          widget.onCountdownExpire();
    );
    controller.reverse(from: 1.0);
   
   ...
   ...
   ... // omitted code

所以一旦动画完成就会调用回调函数:

class _QuizPageState extends State<QuizPage> 
  ... // omitted code  

  @override
  void initState() 
   ... // omitted code
  

  void onCountdownExpire() 
    setState(() 
      _topContentImage =  AssetImage(questions[questionNum++].imagePath);
    );
  
  ... // omitted code

我尝试关注solution,但它不起作用并给了我同样的例外:

  void onCountdownExpire() => 
    setState(() 
      _topContentImage =  AssetImage(questions[questionNum++].imagePath);
    );

我也试过了,但没用:

  @override
  void initState() 
    super.initState();
    controller = AnimationController(
      vsync: this,
      duration: Duration(seconds: 2),
    )..addStatusListener((AnimationStatus status) =>
        (status == AnimationStatus.completed) ?
          widget.onCountdownExpire():null
    );
    controller.reverse(from: 1.0);
  

【问题讨论】:

【参考方案1】:

也许尝试包括 'dart:async':

import 'dart:async';

然后尝试将您对 onCountdownExpire 函数的调用封装在一个短暂的 Timer() 中:

...
        Timer(
          Duration(milliseconds:50),
          () 
            if (status == AnimationStatus.completed)
              widget.onCountdownExpire();
          ,
        );
...

这将使setState() 发生在动画最后一帧的构建阶段之外。

该错误最有可能是因为 Countdown() 正在作为 QuizPage() 小部件重绘的一部分而被重绘。添加Timer() 将强制更新在build() 范围之外以异步方式进行,并且仍将获得相同的结果而不会出现错误。

【讨论】:

好的,我试过了,得到了这个异常,而不是I/flutter (16413): Another exception was thrown: RangeError (index): Invalid value: Not in range 0..1, inclusive: 2 您是否进行了完全重启?还是只是热重载?因为此更改发生在 initState() 方法中,所以您需要完全重新启动。此外,消息的截断错误消息(“另一个异常......”)通常意味着在该异常之前引发了先前的异常。了解该异常是什么有助于缩小问题范围。 好的,我想我知道为什么会抛出范围错误,因为questions 只有 2 个元素并且它正在调用 [2],这是错误的。发生这种情况的原因是,当动画开始时,它以某种方式“完成”了,因为我在调用 windget.onCountdownExpire() 之前插入了一个调试打印,它在动画开始时打印,而不是在动画结束时打印。 我完全重启了 好的,开始时状态已完成的原因:controller.reverse(from: 1.0);。在我将其更改为controller.animateTo(1.0); 后,它在动画结束时完成。

以上是关于在构建异常期间调用的 setState() 或 markNeedsBuild() 阻止我执行回调的主要内容,如果未能解决你的问题,请参考以下文章

Flutter - 在构建期间调用 setState() 或 markNeedsBuild()

在构建期间调用 setState() 或 markNeedsBuild() - Flutter

Flutter:在构建期间调用 setState() 或 markNeedsBuild()

在构建 CupertinoTabScaffold 期间调用 setState() 或 markNeedsBuild()

错误:在构建期间调用了 setState() 或 markNeedsBuild()

Flutter:在构建错误期间调用了 setState() 或 markNeedsBuild()