#flutter #dart #mobx
Вопрос:
В нескольких местах нашего приложения есть исключения, подобные этому:
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
помощью . И мне не нравится такое решение, но, по крайней мере, без ошибок. Но в коде есть места, где этот хак не работает.
Я действительно хочу понять, как с этим справиться без взломов. Проблема в том, что я не смог предоставить воспроизводимый код, потому что я не могу воспроизвести это поведение в простом демонстрационном приложении. И я понятия не имею, почему.
_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);
}
// ...
}
and CatalogState
like that:
class CatalogState = _CatalogStateBase with _$CatalogState;
abstract class _CatalogStateBase with Store {
@observable
int catalogIndex = 0;
@action
void changeCatalogIndex(int index) {
catalogIndex = index;
}
}
Странно то, что ошибка не возникает, когда я просто открываю представление каталога, но это происходит, если я открываю элемент представления каталога, а затем на странице сведений об элементе я возвращаюсь на страницу представления каталога. В этом случае Flutter создает новое новое представление каталога, и постоянно появляется ошибка.
Где-то обнаружил, что я должен менять магазин только во время реакции. Но в этом случае какую реакцию мне следует выбрать? Кроме того, мне нужно запустить этот код только один раз. Я пробовал реакцию автозапуска, но она просто нарушает код, и ничто не работает так, как должно.
Если я не могу мутировать в хранилище initState
(несмотря на то, что в большинстве случаев это хорошо работает), то где для этого альтернативное место?
Кроме того, я пробовал это:
autorun((_) => _catalogState.catalogIndex, (int index){
_catalogState.changeCatalogIndex(widget.defaultIndex);
});
и это:
untracked(() {
_catalogState.changeCatalogIndex(widget.defaultIndex);
});
но не повезло(. Странно, что untracked
это не удается, потому что неотслеженные изменения хранилища не должны запускать процесс признания недействительными.
Похоже, что бывают случаи, когда initState вызывается во время фазы сборки родительского виджета. И именно поэтому возникает ошибка.
Любая помощь или идея о том, как ее отладить и исправить, приветствуется.
Комментарии:
1. эта ошибка возникла при настройке состояния приложения во время сборки виджета,
2. Проблема не
initState
в вас, а в вашемbuild
методе. Вам нужно проверить, вызываете ли вы setState или поставщиков обновлений/состояние mobx при создании представления3. Я ничего не вижу в методе сборки. Кроме того, если бы что-то было, это вообще не должно было бы работать, но в большинстве случаев это работает. Также, если я прокомментирую
changeCatalogIndex
, то ошибок не будет.
Ответ №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);
}
Комментарии:
1. Я уже упоминал, что здесь это
addPostFrameCallback
не работает. Кроме того, я хочу избавиться отaddPostFrameCallback
всей кодовой базы. Мне нужно чистое и идиоматичное решение.2. @ChessMax ты нашел какое-нибудь решение?
3. @ShashankSrivastava Я не нашел хорошего решения. Теперь используется
addPostFrameCallback
в качестве обходного пути.