Flutter setState() или markNeedsBuild() вызывается, когда дерево виджетов было заблокировано

У меня возникают проблемы с поиском источника этого исключения, и приложение довольно сложное, поэтому трудно показать какую-либо соответствующую часть кода. Это мой репозиторий в момент возникновения ошибки: 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 отправляются непосредственно друг за другом. Пользовательский интерфейс за диалогом подписывается на изменения в хранилище 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): ════════════════════════════════════════════════════════════════════════════════════════════════════

9 ответов

Решение

Это не проблема потока. Эта ошибка означает, что вы звоните setState на этапе сборки.

Типичный пример будет следующим:

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

Но вызов setState может быть менее очевидным. Например, Navigator.pop(context) делает setState внутренне. Итак, следующее:

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

это тоже не ходи.


Глядя на трассировку стека, кажется, что одновременно с Navigator.pop(context) ваш модал попробуйте обновить новыми данными.

В качестве обходного пути оберните свой код, который вызывает setState в WidgetsBinding.addPostFrameCallback:

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

Таким образом, вы можете быть уверены, что он будет запущен после создания текущего виджета.

Частный случай использованияScaffold а также Drawer:

Этот сбой также может произойти, если вы попытаетесь восстановить дерево с помощью открытого Drawer. Например, если вы отправляете сообщениеBloc это заставляет перестроить всю страницу / экран.

Считайте, чтобы позвонить Navigator.pop(context) первым в вашем обработчике крана.

Используется несколько вариантов setState внутри build метод и context на initState

Оберните свой setState внутри одного из этих

WidgetsBinding.instance.addPostFrameCallback или же Future.microTask() или же Timer.run или же Future.delayed(Duration.zero,

Пример:

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

Поскольку предоставленный ответ кажется правдивым, у меня был обходной путь, поскольку в моем случае было невозможно просто удалить его из сборки.

я использовал Future.delayed(Duration.zero, () => setState(() { ... })); вместо setState и то же самое для методов, которые также могут использовать setState.

РЕДАКТИРОВАТЬ: источник

Использовать Timer учебный класс,

import 'dart:async';

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

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

Я столкнулся с той же проблемой при создании виджета, который нужно сделать Navigator.pop перед возвратом виджета в build.

Позвольте назвать код Реми Русселе:

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

Этот код, приведенный выше, вызовет ошибку, но вы можете открыть ее только потому, что context в build, так как это сделать?

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();
}

Этот метод представляет собой комбинацию других, но я не уверен, что это лучший метод, если есть какие-либо ошибки, пожалуйста, оставьте комментарий, чтобы указать мне!

Я контролирую пользователя на 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');
      });
    }
  }
       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');
  });
}

}

Другие вопросы по тегам