【问题标题】:MobX and setState() or markNeedsBuild() called during build exception在构建异常期间调用 MobX 和 setState() 或 markNeedsBuild()
【发布时间】:2021-08-08 13:47:11
【问题描述】:

在我们应用的几个地方有这样的例外:

EXCEPTION CAUGHT BY FLUTTER_MOBX 
The following MobXCaughtException was thrown:
setState() or markNeedsBuild() called during build.
This Observer widget cannot be marked as needing to build because the framework is already in the
process of building widgets.  A widget can be marked as needing to be built during the build phase
only if one of its ancestors is currently building. This exception is allowed because the framework
builds parent widgets before children, which means a dirty descendant will always be built.
Otherwise, the framework might not visit this widget during this build phase.
The widget on which setState() or markNeedsBuild() was called was:
  Observer
The widget which was currently being built when the offending call was made was:
  Builder

When the exception was thrown, this was the stack:
#0      Element.markNeedsBuild.<anonymous closure> (package:flutter/src/widgets/framework.dart:4292:11)
#1      Element.markNeedsBuild (package:flutter/src/widgets/framework.dart:4307:6)
#2      ObserverElementMixin.invalidate (package:flutter_mobx/src/observer_widget_mixin.dart:70:24)
#3      ReactionImpl._run (package:mobx/src/core/reaction.dart:119:22)
#4      ReactiveContext._runReactionsInternal (package:mobx/src/core/context.dart:345:18)
#5      ReactiveContext.runReactions (package:mobx/src/core/context.dart:319:5)
#6      ReactiveContext.endBatch (package:mobx/src/core/context.dart:149:7)
#7      ActionController.endAction (package:mobx/src/core/action.dart:107:9)
#8      _$CatalogState.changeCatalogIndex (package:my_app/catalog/state/catalog_state.g.dart:37:43)
#9      _CatalogViewState.initState (package:my_app/catalog/catalog_view.dart:277:19)
#10     StatefulElement._firstBuild (package:flutter/src/widgets/framework.dart:4765:58)
#11     ComponentElement.mount (package:flutter/src/widgets/framework.dart:4601:5)
...     Normal element mounting (112 frames)
#123    Element.inflateWidget (package:flutter/src/widgets/framework.dart:3569:14)
#124    Element.updateChild (package:flutter/src/widgets/framework.dart:3327:18)
#125    RenderObjectElement.updateChildren (package:flutter/src/widgets/framework.dart:5705:32)
#126    MultiChildRenderObjectElement.update (package:flutter/src/widgets/framework.dart:6246:17)
#127    Element.updateChild (package:flutter/src/widgets/framework.dart:3314:15)
#128    ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:4652:16)
#129    StatefulElement.performRebuild (package:flutter/src/widgets/framework.dart:4800:11)
#130    Element.rebuild (package:flutter/src/widgets/framework.dart:4343:5)
#131    StatefulElement.update (package:flutter/src/widgets/framework.dart:4832:5)
#132    Element.updateChild (package:flutter/src/widgets/framework.dart:3314:15)
#133    ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:4652:16)
#134    Element.rebuild (package:flutter/src/widgets/framework.dart:4343:5)
#135    ProxyElement.update (package:flutter/src/widgets/framework.dart:4987:5)
#136    _InheritedNotifierElement.update (package:flutter/src/widgets/inherited_notifier.dart:183:11)
#137    Element.updateChild (package:flutter/src/widgets/framework.dart:3314:15)
#138    SingleChildRenderObjectElement.update (package:flutter/src/widgets/framework.dart:6125:14)
#139    Element.updateChild (package:flutter/src/widgets/framework.dart:3314:15)
#140    ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:4652:16)
#141    StatefulElement.performRebuild (package:flutter/src/widgets/framework.dart:4800:11)
#142    Element.rebuild (package:flutter/src/widgets/framework.dart:4343:5)
#143    StatefulElement.update (package:flutter/src/widgets/framework.dart:4832:5)
#144    Element.updateChild (package:flutter/src/widgets/framework.dart:3314:15)
#145    SingleChildRenderObjectElement.update (package:flutter/src/widgets/framework.dart:6125:14)
#146    Element.updateChild (package:flutter/src/widgets/framework.dart:3314:15)
#147    SingleChildRenderObjectElement.update (package:flutter/src/widgets/framework.dart:6125:14)
#148    Element.updateChild (package:flutter/src/widgets/framework.dart:3314:15)
#149    ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:4652:16)
#150    Element.rebuild (package:flutter/src/widgets/framework.dart:4343:5)
#151    StatelessElement.update (package:flutter/src/widgets/framework.dart:4708:5)
#152    Element.updateChild (package:flutter/src/widgets/framework.dart:3314:15)
#153    ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:4652:16)
#154    Element.rebuild (package:flutter/src/widgets/framework.dart:4343:5)
#155    ProxyElement.update (package:flutter/src/widgets/framework.dart:4987:5)
#156    Element.updateChild (package:flutter/src/widgets/framework.dart:3314:15)
#157    ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:4652:16)
#158    StatefulElement.performRebuild (package:flutter/src/widgets/framework.dart:4800:11)
#159    Element.rebuild (package:flutter/src/widgets/framework.dart:4343:5)
#160    BuildOwner.buildScope (package:flutter/src/widgets/framework.dart:2730:33)
#161    WidgetsBinding.drawFrame (package:flutter/src/widgets/binding.dart:913:20)
#162    RendererBinding._handlePersistentFrameCallback (package:flutter/src/rendering/binding.dart:302:5)
#163    SchedulerBinding._invokeFrameCallback (package:flutter/src/scheduler/binding.dart:1117:15)
#164    SchedulerBinding.handleDrawFrame (package:flutter/src/scheduler/binding.dart:1055:9)
#165    SchedulerBinding._handleDrawFrame (package:flutter/src/scheduler/binding.dart:971:5)
#169    _invoke (dart:ui/hooks.dart:251:10)
#170    _drawFrame (dart:ui/hooks.dart:209:3)
(elided 3 frames from dart:async)

有时可以通过使用SchedulerBinding.instance.addPostFrameCallback 推迟状态修改来避免此类错误。而且我不喜欢这样的解决方案,但至少没有错误。但是在代码中有些地方这种 hack 不起作用。 我真的很想了解如何在没有黑客攻击的情况下处理它。问题是我无法提供可重现的代码,因为我无法在简单的演示应用程序中重现此行为。我不知道为什么。 _CatalogViewState 看起来像这样:

class _CatalogViewState extends State<CatalogView> {
  PageController _pageController;
  TabController _tabController;

  ProductsState _productsState;
  FarmersState _farmersState;
  ToursState _toursState;
  DishesState _dishesState;
  CatalogState _catalogState;
  CategoriesState _categoriesState;
  FiltersState _filtersState;
  GeoState _geoState;

  int get _index() => _catalogState.catalogIndex;

  // ...
 
  @override
  void initState() {
    super.initState();
    _productsState = Provider.of<ProductsState>(context, listen: false);
    _farmersState = Provider.of<FarmersState>(context, listen: false);
    _toursState = Provider.of<ToursState>(context, listen: false);
    _dishesState = Provider.of<DishesState>(context, listen: false);
    _catalogState = Provider.of<CatalogState>(context, listen: false);
    _categoriesState = Provider.of<CategoriesState>(context, listen: false);
    _filtersState = Provider.of<FiltersState>(context, listen: false);
    _geoState = Provider.of<GeoState>(context, listen: false);
    if (widget.defaultCategorySlug != null) {
      SchedulerBinding.instance.addPostFrameCallback((_) => _initDefaultSelectedCategory());
    }
    _clearEmptyDishesCategories(); 
    _catalogState.changeCatalogIndex(widget.defaultIndex);
    _pageController = PageController(initialPage: _index);
    _pageController.addListener(_switchTabColor);
  }

  // ...
}

CatalogState 这样:

class CatalogState = _CatalogStateBase with _$CatalogState;

abstract class _CatalogStateBase with Store {
  @observable
  int catalogIndex = 0;

  @action
  void changeCatalogIndex(int index) {
    catalogIndex = index;
  }
}

奇怪的是,当我打开 CatalogView 时不会抛出错误,但是如果我打开目录视图项目,然后在项目详细信息页面上,我会返回到目录视图页面。在这种情况下,Flutter 会创建一个全新的 CatalogView,并且会不断出现错误。

在某个地方发现我应该只在反应期间更换商店。但在这种情况下,我应该选择什么反应?此外,我只需要运行此代码一次。我已经尝试过自动运行反应,但它只是破坏了代码并且没有任何工作正常。

如果我不能改变 initState 中的存储(尽管在大多数情况下它运行良好),那么它的替代位置在哪里?

我也试过这个:

autorun((_) => _catalogState.catalogIndex, (int index){
   _catalogState.changeCatalogIndex(widget.defaultIndex);
});

还有这个:

untracked(() {
  _catalogState.changeCatalogIndex(widget.defaultIndex);
});

但没有运气(。奇怪的是untracked 失败了,因为未跟踪的存储更改不应该启动失效过程。

感觉就像在父窗口小部件构建阶段调用 initState 的情况。这就是发生错误的原因。

对于如何调试和修复它的任何帮助或想法表示赞赏。

【问题讨论】:

  • 在小部件构建期间设置应用状态时出现此错误,
  • 问题不在于您的initState,而在于您的build 方法。在构建视图时,您需要检查是否调用了 setState 或更新提供者/mobx 状态
  • 我在构建方法中看不到任何内容。此外,如果有什么东西它根本不应该工作,但在大多数情况下,它是有效的。此外,如果我评论 changeCatalogIndex 则没有错误。

标签: flutter dart mobx


【解决方案1】:

当您在initState 中尝试触发重建时会发生此错误。如果您想在初始化逻辑上触发重建,您可以将该重建触发器包装在 postFrameCallback 中。在这种情况下,我认为问题出在catalogState

@override
  void initState() {
    super.initState();
    _productsState = Provider.of<ProductsState>(context, listen: false);
    _farmersState = Provider.of<FarmersState>(context, listen: false);
    _toursState = Provider.of<ToursState>(context, listen: false);
    _dishesState = Provider.of<DishesState>(context, listen: false);
    _catalogState = Provider.of<CatalogState>(context, listen: false);
    _categoriesState = Provider.of<CategoriesState>(context, listen: false);
    _filtersState = Provider.of<FiltersState>(context, listen: false);
    _geoState = Provider.of<GeoState>(context, listen: false);
    if (widget.defaultCategorySlug != null) {
      SchedulerBinding.instance.addPostFrameCallback((_) => _initDefaultSelectedCategory());
    }
    _clearEmptyDishesCategories(); 
    // this triggers a rebuild 
    // _catalogState.changeCatalogIndex(widget.defaultIndex);
    // you can do this in the first frame after initial build
    WidgetsBinding.instance.addPostFrameCallback((_) => _catalogState.changeCatalogIndex(widget.defaultIndex));
    _pageController = PageController(initialPage: _index);
    _pageController.addListener(_switchTabColor);
  }

【讨论】:

  • 我已经提到addPostFrameCallback 在这里不起作用。另外,我想在整个代码库中摆脱addPostFrameCallback。我需要一个干净且惯用的解决方案。
  • @ChessMax 你有什么解决办法吗?
  • @ShashankSrivastava 我没有找到好的解决方案。现在使用addPostFrameCallback 作为解决方法。
猜你喜欢
  • 2021-08-07
  • 2018-05-15
  • 2020-10-05
  • 2020-12-12
  • 2020-12-28
  • 2021-01-06
  • 2021-06-06
  • 2021-05-31
相关资源
最近更新 更多