【问题标题】:Flutter setState() or markNeedsBuild() called when widget tree was lockedFlutter setState() 或 markNeedsBuild() 在小部件树被锁定时调用
【发布时间】:2018-01-06 15:39:49
【问题描述】:

我无法找到此异常的来源,而且该应用程序非常复杂,因此很难显示代码的任何相关部分。这是我发生错误时的存储库: Github repo

我怀疑以下代码可能是肇事者:

Widget buildResultCard(Map result, BuildContext context) {
    String name = result["value"];
    String description =
        result["label"].replaceAll(new RegExp(r"<(?:.|\n)*?>"), "");
    TextEditingController controller = new TextEditingController(text: name);

    Function onPressed = () {
      showDialog(
          context: context,
          child: new AlertDialog(
            title: new Text("Name your schedule"),
            content: new TextField(
              autofocus: true,
              controller: controller,
            ),
            actions: <Widget>[
              new FlatButton(
                  onPressed: () {
                    String givenName = controller.text;
                    ScheduleMeta schedule = new ScheduleMeta(
                        givenName: givenName,
                        name: name,
                        type: _selectedChoice.value,
                        description: description);

                    scheduleStore
                        .dispatch(new AddScheduleAction(schedule: schedule));

                    scheduleStore.dispatch(
                        new SetCurrentScheduleAction(schedule: schedule));

                    fetchAllSchedules(scheduleStore.state.schedules)
                        .then((weeks) {
                      scheduleStore.dispatch(
                          new SetWeeksForCurrentScheduleAction(weeks: weeks));
                    });

                    Scaffold.of(context).showSnackBar(new SnackBar(
                          content: new Text("Added " + givenName),
                          action: new SnackBarAction(
                              label: "Undo",
                              onPressed: () {
                                scheduleStore.dispatch(
                                    new RemoveScheduleAction(schedule: name));

                                Scaffold.of(context).showSnackBar(new SnackBar(
                                      content: new Text(
                                          "Deleted " + givenName),
                                    ));
                              }),
                        ));

                    Navigator.of(context).pop();
                  },
                  child: new Text("Add")),
            ],
          ));
    };

    return new Card(
      child: new Column(
        mainAxisSize: MainAxisSize.min,
        children: <Widget>[
          new ListTile(
            leading: const Icon(Icons.schedule),
            title: new Text(name),
            subtitle: new Text(description),
            isThreeLine: true,
            dense: true,
          ),
          new ButtonTheme.bar(
            child: new ButtonBar(
              children: <Widget>[
                new FlatButton(
                    child: const Text('Add Schedule'),
                    onPressed: scheduleStore.state.schedules
                            .any((schedule) => schedule.name == name)
                        ? null
                        : onPressed)
              ],
            ),
          ),
        ],
      ),
    );
  }

显示对话框时出现错误,当用户按下对话框上的按钮时,两个 Redux 存储调度是直接相互发送的。对话框后面的 UI 订阅 Redux 存储中的更改。

我认为 Dart/Flutter 是单线程的,所以不会发生这样的冲突,似乎一个线程在一个小部件上调用 setState(),而另一个线程在小部件树上加了一个锁。

有没有办法检查小部件树是否被锁定,这样可以避免这种情况?

堆栈提供此信息:

I/flutter (13466): ══╡ EXCEPTION CAUGHT BY WIDGETS LIBRARY ╞═══════════════════════════════════════════════════════════
I/flutter (13466): The following assertion was thrown while finalizing the widget tree:
I/flutter (13466): setState() or markNeedsBuild() called when widget tree was locked.
I/flutter (13466): This _ModalScope widget cannot be marked as needing to build because the framework is locked.
I/flutter (13466): The widget on which setState() or markNeedsBuild() was called was:
I/flutter (13466):   _ModalScope([LabeledGlobalKey<_ModalScopeState>#cb4cc]; state: _ModalScopeState#d4c02())
I/flutter (13466): 
I/flutter (13466): When the exception was thrown, this was the stack:
I/flutter (13466): #0      Element.markNeedsBuild.<anonymous closure> (package:flutter/src/widgets/framework.dart:3250)
I/flutter (13466): #2      Element.markNeedsBuild (package:flutter/src/widgets/framework.dart:3226)
I/flutter (13466): #3      State.setState (package:flutter/src/widgets/framework.dart:1072)
I/flutter (13466): #4      _ModalScopeState._routeSetState (package:flutter/src/widgets/routes.dart:473)
I/flutter (13466): #5      ModalRoute.setState (package:flutter/src/widgets/routes.dart:552)
I/flutter (13466): #6      ModalRoute.changedInternalState (package:flutter/src/widgets/routes.dart:889)
I/flutter (13466): #7      TransitionRoute&&LocalHistoryRoute.removeLocalHistoryEntry (package:flutter/src/widgets/routes.dart:317)
I/flutter (13466): #8      LocalHistoryEntry.remove (package:flutter/src/widgets/routes.dart:267)
I/flutter (13466): #9      DrawerControllerState.dispose (package:flutter/src/material/drawer.dart:147)
I/flutter (13466): #10     StatefulElement.unmount (package:flutter/src/widgets/framework.dart:3550)
I/flutter (13466): #11     _InactiveElements._unmount (package:flutter/src/widgets/framework.dart:1626)
I/flutter (13466): #12     _InactiveElements._unmount.<anonymous closure> (package:flutter/src/widgets/framework.dart:1624)
I/flutter (13466): #13     ComponentElement.visitChildren (package:flutter/src/widgets/framework.dart:3427)
I/flutter (13466): #14     _InactiveElements._unmount (package:flutter/src/widgets/framework.dart:1622)
I/flutter (13466): #15     _InactiveElements._unmount.<anonymous closure> (package:flutter/src/widgets/framework.dart:1624)
I/flutter (13466): #16     MultiChildRenderObjectElement.visitChildren (package:flutter/src/widgets/framework.dart:4421)
I/flutter (13466): #17     _InactiveElements._unmount (package:flutter/src/widgets/framework.dart:1622)
I/flutter (13466): #18     _InactiveElements._unmount.<anonymous closure> (package:flutter/src/widgets/framework.dart:1624)
I/flutter (13466): #19     ComponentElement.visitChildren (package:flutter/src/widgets/framework.dart:3427)
I/flutter (13466): #20     _InactiveElements._unmount (package:flutter/src/widgets/framework.dart:1622)
I/flutter (13466): #21     _InactiveElements._unmount.<anonymous closure> (package:flutter/src/widgets/framework.dart:1624)
I/flutter (13466): #22     ComponentElement.visitChildren (package:flutter/src/widgets/framework.dart:3427)
I/flutter (13466): #23     _InactiveElements._unmount (package:flutter/src/widgets/framework.dart:1622)
I/flutter (13466): #24     _InactiveElements._unmount.<anonymous closure> (package:flutter/src/widgets/framework.dart:1624)
I/flutter (13466): #25     SingleChildRenderObjectElement.visitChildren (package:flutter/src/widgets/framework.dart:4321)
I/flutter (13466): #26     _InactiveElements._unmount (package:flutter/src/widgets/framework.dart:1622)
I/flutter (13466): #27     _InactiveElements._unmount.<anonymous closure> (package:flutter/src/widgets/framework.dart:1624)
I/flutter (13466): #28     ComponentElement.visitChildren (package:flutter/src/widgets/framework.dart:3427)
I/flutter (13466): #29     _InactiveElements._unmount (package:flutter/src/widgets/framework.dart:1622)
I/flutter (13466): #30     _InactiveElements._unmount.<anonymous closure> (package:flutter/src/widgets/framework.dart:1624)
I/flutter (13466): #31     SingleChildRenderObjectElement.visitChildren (package:flutter/src/widgets/framework.dart:4321)
I/flutter (13466): #32     _InactiveElements._unmount (package:flutter/src/widgets/framework.dart:1622)
I/flutter (13466): #33     _InactiveElements._unmount.<anonymous closure> (package:flutter/src/widgets/framework.dart:1624)
I/flutter (13466): #34     ComponentElement.visitChildren (package:flutter/src/widgets/framework.dart:3427)
I/flutter (13466): #35     _InactiveElements._unmount (package:flutter/src/widgets/framework.dart:1622)
I/flutter (13466): #36     _InactiveElements._unmount.<anonymous closure> (package:flutter/src/widgets/framework.dart:1624)
I/flutter (13466): #37     ComponentElement.visitChildren (package:flutter/src/widgets/framework.dart:3427)
I/flutter (13466): #38     _InactiveElements._unmount (package:flutter/src/widgets/framework.dart:1622)
I/flutter (13466): #39     _InactiveElements._unmount.<anonymous closure> (package:flutter/src/widgets/framework.dart:1624)
I/flutter (13466): #40     ComponentElement.visitChildren (package:flutter/src/widgets/framework.dart:3427)
I/flutter (13466): #41     _InactiveElements._unmount (package:flutter/src/widgets/framework.dart:1622)
I/flutter (13466): #42     _InactiveElements._unmount.<anonymous closure> (package:flutter/src/widgets/framework.dart:1624)
I/flutter (13466): #43     ComponentElement.visitChildren (package:flutter/src/widgets/framework.dart:3427)
I/flutter (13466): #44     _InactiveElements._unmount (package:flutter/src/widgets/framework.dart:1622)
I/flutter (13466): #45     _InactiveElements._unmount.<anonymous closure> (package:flutter/src/widgets/framework.dart:1624)
I/flutter (13466): #46     ComponentElement.visitChildren (package:flutter/src/widgets/framework.dart:3427)
I/flutter (13466): #47     _InactiveElements._unmount (package:flutter/src/widgets/framework.dart:1622)
I/flutter (13466): #48     _InactiveElements._unmountAll (package:flutter/src/widgets/framework.dart:1636)
I/flutter (13466): #49     BuildOwner.finalizeTree.<anonymous closure> (package:flutter/src/widgets/framework.dart:2228)
I/flutter (13466): #50     BuildOwner.lockState (package:flutter/src/widgets/framework.dart:2060)
I/flutter (13466): #51     BuildOwner.finalizeTree (package:flutter/src/widgets/framework.dart:2227)
I/flutter (13466): #52     BindingBase&SchedulerBinding&GestureBinding&ServicesBinding&RendererBinding&WidgetsBinding.drawFrame (package:flutter/src/widgets/binding.dart:505)
I/flutter (13466): #53     BindingBase&SchedulerBinding&GestureBinding&ServicesBinding&RendererBinding._handlePersistentFrameCallback (package:flutter/src/rendering/binding.dart:189)
I/flutter (13466): #54     BindingBase&SchedulerBinding._invokeFrameCallback (package:flutter/src/scheduler/binding.dart:688)
I/flutter (13466): #55     BindingBase&SchedulerBinding.handleDrawFrame (package:flutter/src/scheduler/binding.dart:636)
I/flutter (13466): #56     _drawFrame (file:///b/build/slave/Linux_Engine/build/src/flutter/lib/ui/hooks.dart:70)
I/flutter (13466): (elided one frame from class _AssertionError)
I/flutter (13466): ════════════════════════════════════════════════════════════════════════════════════════════════════

【问题讨论】:

  • 不是一个完整的答案,但我得到了同样的错误,用下面的方法创建一个带抽屉的脚手架,在抽屉里放一个按钮。将回调绑定到按钮,导致应用程序状态以不再呈现整个抽屉的方式更改。然后错误是由抽屉仍然打开但不再在小部件树中引起的。我在调用setState() 之前调用Navigator.pop(context)(即关闭抽屉)的解决方案会导致抽屉被移除。
  • 非常好。当您不自己关闭抽屉并尝试刷新商店时,就会发生这种情况。我们都知道后台刷新存储调用了重建,这会导致抽屉状态变脏并且它也会重建?
  • @LaszloKorte 考虑将其作为答案,因为您的评论是此问题中最真实的答案

标签: redux dart flutter


【解决方案1】:

每次打开或重新加载时,我都会在 LoginScreen 控制用户。如果用户通过身份验证,我会导航到主页。此时我得到同样的错误。据我了解,这是关于导航器自己的 setState 。 addPostFrameCallback 或定时器修复它。

  void initState() {
    super.initState();
    if (auth.getUser != null) {
      // ERROR
      // Navigator.pushReplacementNamed(context, '/home');

      // SUCCESS
      // WidgetsBinding.instance.addPostFrameCallback((_) => setState(() {
      //       Navigator.pushReplacementNamed(context, '/home');
      //     }));

      // SUCCESS
      Timer.run(() {
        Navigator.pushReplacementNamed(context, '/home');
      });
    }
  }

【讨论】:

  • Thaaaaaaaanksss
【解决方案2】:

setStatebuild 方法中使用了几个选项,contextinitState 上使用

将您的 setState 包装在其中一个中

WidgetsBinding.instance.addPostFrameCallbackFuture.microTask()Timer.runFuture.delayed(Duration.zero,

例子:

  WidgetsBinding.instance
              .addPostFrameCallback((_) {
            //valueNotifier.value = _pcm; //provider
            //setState
          });

【讨论】:

  • Thaaaaaaaaaanks
【解决方案3】:

使用Timer类,

import 'dart:async';

@override
void initState() {
  super.initState();

  Timer.run(() {
    // You can call setState from here
  });
}

【讨论】:

    【解决方案4】:

    这不是线程问题。此错误表示您在构建阶段调用了setState

    一个典型的例子如下:

    Widget build(BuildContext context) {
        myParentWidgetState.setState(() { print("foo"); });
        return Container();
    }
    

    但是 setState 调用可能不太明显。例如,Navigator.pop(context) 在内部执行 setState。所以如下:

    Widget build(BuildContext context) {
        Navigator.pop(context);
        return Container();
    }
    

    也不行。


    查看堆栈跟踪,您的模式似乎与Navigator.pop(context) 同时尝试使用新数据进行更新。

    【讨论】:

    • Rousselet,但是 这个异常的含义是什么? 例如,我的应用程序似乎运行良好。我在后台使用notifyListeners()(我事先不知道这会是个问题)在使用FutureBuilder 构建小部件时更新数据类,这给了我同样的异常。
    • @PhilippeFanaro 这意味着你正在做一些反模式的事情。总有一种方法可以在不反模式的情况下实现相同的结果。这是特定情况的thohg
    • 有点遗憾,我现在才发现这个,我可能需要修改大约 10 多天的工作......
    【解决方案5】:

    特殊情况使用ScaffoldDrawer

    如果您尝试使用打开的Drawer 重建树,也会发生这种故障。例如,如果您向Bloc 发送消息,强制重建整个页面/屏幕。

    考虑在您的点击处理程序中首先调用Navigator.pop(context)

    【讨论】:

      【解决方案6】:

      我在制作一个需要做Navigator.pop 的小部件时遇到了同样的问题,然后在build 中返回一个小部件。

      让我拨打Rémi Rousselet的代码:

      Widget build(BuildContext context) {
          Navigator.pop(context);
          return Container();
      }
      

      上面这段代码会导致错误,但是你只能弹出因为contextbuild中,那怎么办?

      Widget build(BuildContext context) {
          if(datas != null) // check datas
          {
            SchedulerBinding.instance.addPostFrameCallback((_){ // make pop action to next cycle
              Navigator.of(context).pop(datas); /*...your datas*/
            });
          }
          return Container();
      }
      

      此方法是其他方法的组合,但我不确定这是否是最佳实践,如果有任何错误,请留言指出我!

      【讨论】:

        【解决方案7】:

        作为一种解决方法,将调用 setState 的代码包装到 WidgetsBinding.addPostFrameCallback:

        WidgetsBinding.instance
                .addPostFrameCallback((_) => setState(() {}));
        

        这样您就可以确保它在当前小部件构建后得到执行。

        【讨论】:

        • 这很好用,但我有一个问题,这是否仅适用于放入此代码的小部件?或适用于屏幕上显示的所有小部件?非常感谢。
        • 谢谢老兄!
        • 两种解决方案都有效。您可以使用WidgetsBinding.instance.addPostFrameCallbackFuture.delayed 任何这些功能。谢谢。
        【解决方案8】:

        由于提供的答案似乎是正确的,我有一个解决方法,因为在我的情况下,不可能简单地从构建中删除它。

        我使用Future.delayed(Duration.zero, () =&gt; setState(() { ... })); 代替 setState 并且对于可能使用 setState 的方法也是如此。

        编辑:source

        【讨论】:

          猜你喜欢
          • 2021-09-16
          • 1970-01-01
          • 1970-01-01
          • 2020-12-12
          • 2020-12-28
          • 2021-01-06
          • 2021-05-31
          • 2021-08-24
          相关资源
          最近更新 更多