flutter - 在构建异常期间调用 MobX 和 setState() 或 markNeedsBuild()
问题描述
在我们的应用程序的几个地方有这样的例外:
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
. 如果要在初始化逻辑上触发重建,可以将该重建触发器包装在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);
}
推荐阅读
- html - 在 Bootstudio 中设置默认扩展网页名称
- ios - 在 SwiftUI 中使用 Firebase 进行用户管理从哪里开始?
- vue.js - 检测 v-carousel 上的分隔符被点击
- expandablelistview - ExpandableListView:允许不同的动作改变视图
- javascript - 如何从 KnockoutJs 获取呈现的 HTML(我有充分的理由)
- python - 该复选框在未选中时不能返回 False,但在选中时可以返回 True
- python - 在 FastAPI 中请求上下文?
- java - 原因:java.io.FileNotFoundException:无法在类路径上找到 clojure/core__init.class 或 clojure/core.clj
- python - 在顶部和右侧创建另外两个轴后,我无法在两个轴上创建网格线。matplotlib 问题
- android - diffutil areContentsTheSame 函数只比较新项目