Flutter Web:Provider类触发setState()/markNeedsBuild()构建阶段调用错误
解决Flutter Web仪表盘Provider状态管理错误:
setState() or markNeedsBuild() called during build. 问题场景
开发Flutter Web侧边栏仪表盘时,使用Provider进行状态管理,调整窗口大小触发RootPage的build方法时,调用ViewModel的expandSidebar或collapseSidebar会抛出以下错误:
Restarted application in 243ms. Gleap already initialized. [GoRouter] Full paths for routes: => /login [GoRouter] setting initial location /events [GoRouter] Using MaterialApp configuration User is signed in! ══╡ EXCEPTION CAUGHT BY FOUNDATION LIBRARY ╞════════════════════════════════════════════════════════ The following assertion was thrown while dispatching notifications for SidebarViewModel: setState() or markNeedsBuild() called during build. This _InheritedProviderScope<SidebarViewModel?> 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: _InheritedProviderScope<SidebarViewModel?> The widget which was currently being built when the offending call was made was: LayoutBuilder When the exception was thrown, this was the stack: dart-sdk/lib/_internal/js_dev_runtime/private/ddc_runtime/errors.dart 266:49 throw_ packages/flutter/src/widgets/framework.dart 4549:11 <fn> packages/flutter/src/widgets/framework.dart 4563:14 markNeedsBuild packages/provider/src/inherited_provider.dart 577:5 markNeedsNotifyDependents packages/flutter/src/foundation/change_notifier.dart 351:24 notifyListeners packages/vamos_events_dashboard/ui/widgets/sidebar/sidebar_view_model.dart 27:5 expandSidebar packages/vamos_events_dashboard/ui/pages/root_page.dart 18:57 <fn> packages/flutter/src/widgets/layout_builder.dart 119:70 layoutCallback packages/flutter/src/widgets/framework.dart 2605:19 buildScope packages/flutter/src/widgets/layout_builder.dart 153:5 [_layout] packages/flutter/src/rendering/object.dart 2246:59 <fn> packages/flutter/src/rendering/object.dart 1035:15 [_enableMutationsToDirtySubtrees] packages/flutter/src/rendering/object.dart 2246:7 invokeLayoutCallback packages/flutter/src/widgets/layout_builder.dart 228:7 rebuildIfNecessary packages/flutter/src/widgets/layout_builder.dart 316:5 performLayout packages/flutter/src/rendering/object.dart 2135:7 layout packages/flutter/src/rendering/box.dart 2418:11 layout packages/flutter/src/rendering/custom_layout.dart 171:10 layoutChild packages/flutter/src/material/scaffold.dart 1055:7 performLayout packages/flutter/src/rendering/custom_layout.dart 240:7 [_callPerformLayout] packages/flutter/src/rendering/custom_layout.dart 410:14 performLayout packages/flutter/src/rendering/object.dart 2135:7 layout packages/flutter/src/rendering/box.dart 2418:11 layout packages/flutter/src/rendering/proxy_box.dart 120:7 performLayout packages/flutter/src/rendering/object.dart 2135:7 layout packages/flutter/src/rendering/box.dart 2418:11 layout packages/flutter/src/rendering/proxy_box.dart 120:7 performLayout packages/flutter/src/rendering/proxy_box.dart 1462:11 performLayout packages/flutter/src/rendering/object.dart 2135:7 layout packages/flutter/src/rendering/box.dart 2418:11 layout packages/flutter/src/rendering/proxy_box.dart 120:7 performLayout packages/flutter/src/rendering/object.dart 2135:7 layout packages/flutter/src/rendering/box.dart 2418:11 layout packages/flutter/src/rendering/proxy_box.dart 120:7 performLayout packages/flutter/src/rendering/object.dart 2135:7 layout packages/flutter/src/rendering/box.dart 2418:11 layout packages/flutter/src/rendering/proxy_box.dart 120:7 performLayout packages/flutter/src/rendering/object.dart 2135:7 layout packages/flutter/src/rendering/box.dart 2418:11 layout packages/flutter/src/rendering/layout_helper.dart 56:10 layoutChild packages/flutter/src/rendering/stack.dart 595:43 [_computeSize] packages/flutter/src/rendering/stack.dart 622:12 performLayout packages/flutter/src/rendering/object.dart 2135:7 layout packages/flutter/src/rendering/box.dart 2418:11 layout packages/flutter/src/rendering/proxy_box.dart 120:7 performLayout packages/flutter/src/rendering/object.dart 2135:7 layout packages/flutter/src/rendering/box.dart 2418:11 layout packages/flutter/src/rendering/proxy_box.dart 120:7 performLayout packages/flutter/src/rendering/object.dart 2135:7 layout packages/flutter/src/rendering/box.dart 2418:11 layout packages/flutter/src/rendering/proxy_box.dart 120:7 performLayout packages/flutter/src/rendering/object.dart 2135:7 layout packages/flutter/src/rendering/box.dart 2418:11 layout packages/flutter/src/rendering/proxy_box.dart 120:7 performLayout packages/flutter/src/rendering/object.dart 2135:7 layout packages/flutter/src/rendering/box.dart 2418:11 layout packages/flutter/src/rendering/proxy_box.dart 120:7 performLayout packages/flutter/src/rendering/object.dart 2135:7 layout packages/flutter/src/rendering/box.dart 2418:11 layout packages/flutter/src/rendering/proxy_box.dart 120:7 performLayout packages/flutter/src/rendering/proxy_box.dart 3737:13 performLayout packages/flutter/src/rendering/object.dart 2135:7 layout packages/flutter/src/rendering/box.dart 2418:11 layout packages/flutter/src/rendering/proxy_box.dart 120:7 performLayout packages/flutter/src/rendering/object.dart 2135:7 layout packages/flutter/src/rendering/box.dart 2418:11 layout packages/flutter/src/widgets/overlay.dart 804:14 performLayout packages/flutter/src/rendering/object.dart 2135:7 layout packages/flutter/src/rendering/box.dart 2418:11 layout packages/flutter/src/rendering/proxy_box.dart 120:7 performLayout packages/flutter/src/rendering/object.dart 2135:7 layout packages/flutter/src/rendering/box.dart 2418:11 layout packages/flutter/src/rendering/proxy_box.dart 120:7 performLayout packages/flutter/src/rendering/object.dart 2135:7 layout packages/flutter/src/rendering/box.dart 2418:11 layout packages/flutter/src/rendering/proxy_box.dart 120:7 performLayout packages/flutter/src/rendering/object.dart 2135:7 layout packages/flutter/src/rendering/box.dart 2418:11 layout packages/flutter/src/rendering/proxy_box.dart 120:7 performLayout packages/flutter/src/rendering/custom_paint.dart 552:11 performLayout packages/flutter/src/rendering/object.dart 2135:7 layout packages/flutter/src/rendering/box.dart 2418:11 layout packages/flutter/src/rendering/proxy_box.dart 120:7 performLayout packages/flutter/src/rendering/object.dart 2135:7 layout packages/flutter/src/rendering/box.dart 2418:11 layout packages/flutter/src/rendering/proxy_box.dart 120:7 performLayout packages/flutter/src/rendering/object.dart 2135:7 layout packages/flutter/src/rendering/box.dart 2418:11 layout packages/flutter/src/rendering/proxy_box.dart 120:7 performLayout packages/flutter/src/rendering/object.dart 2135:7 layout packages/flutter/src/rendering/box.dart 2418:11 layout packages/flutter/src/rendering/proxy_box.dart 120:7 performLayout packages/flutter/src/rendering/object.dart 2135:7 layout packages/flutter/src/rendering/box.dart 2418:11 layout packages/flutter/src/rendering/view.dart 170:7 performLayout packages/flutter/src/rendering/object.dart 1973:7 [_layoutWithoutResize] packages/flutter/src/rendering/object.dart 999:17 flushLayout packages/flutter/src/rendering/binding.dart 513:19 drawFrame packages/flutter/src/widgets/binding.dart 884:13 drawFrame packages/flutter/src/rendering/binding.dart 378:5 [_handlePersistentFrameCallback] packages/flutter/src/scheduler/binding.dart 1175:15 [_invokeFrameCallback] packages/flutter/src/scheduler/binding.dart 1104:9 handleDrawFrame packages/flutter/src/scheduler/binding.dart 881:7 <fn> dart-sdk/lib/_internal/js_dev_runtime/private/isolate_helper.dart 48:19 internalCallback The SidebarViewModel sending notification was: Instance of 'SidebarViewModel' ════════════════════════════════════════════════════════════════════════════════════════════════════
相关代码
RootPage
class RootPage extends StatelessWidget { const RootPage({required this.widget}); final Widget widget; @override Widget build(BuildContext context) { final SidebarViewModel sidebarViewModel = context.read<SidebarViewModel>(); return Scaffold( body: LayoutBuilder( builder: (_, BoxConstraints constraints) { constraints.maxWidth > 1100 ? sidebarViewModel.expandSidebar() : sidebarViewModel.collapseSidebar(); return Row(children: [const Sidebar(), Expanded(child: widget)]); }, ), ); } }
Sidebar
class Sidebar extends StatelessWidget { const Sidebar({super.key}); @override Widget build(BuildContext context) { final SidebarViewModel sidebarViewModel = context.watch<SidebarViewModel>(); // final SidebarViewModel sidebarViewModel = Provider.of<SidebarViewModel>(context, listen: false); return SizedBox( width: sidebarViewModel.isExpanded ? 250 : 90, child: Padding( padding: const EdgeInsets.all(20.0), child: Column( children: [ SidebarEntry( title: 'Profil', icon: Icons.account_circle_outlined, onTap: () { if (sidebarViewModel.selectedItem != SidebarItem.profile) { sidebarViewModel.selectItem(SidebarItem.profile); context.go(ProfilePage.route); } }, isSelected: sidebarViewModel.selectedItem == SidebarItem.profile, ), SidebarEntry( title: 'Organisation', icon: Icons.business_center_outlined, onTap: () { if (sidebarViewModel.selectedItem != SidebarItem.organization) { sidebarViewModel.selectItem(SidebarItem.organization); context.go(OrganizationPage.route); } }, isSelected: sidebarViewModel.selectedItem == SidebarItem.organization, ), SidebarEntry( title: 'Veranstaltungen', icon: Icons.calendar_month_rounded, onTap: () { if (sidebarViewModel.selectedItem != SidebarItem.events) { sidebarViewModel.selectItem(SidebarItem.events); context.go(EventsPage.route); } }, isSelected: sidebarViewModel.selectedItem == SidebarItem.events, ), const Spacer(), SidebarEntry( title: 'Abmelden', icon: Icons.logout, onTap: () => context.read<AuthService>().signOut(), ), ], ), ), ); } }
SidebarViewModel
enum SidebarItem { profile, organization, events } class SidebarViewModel with ChangeNotifier { SidebarViewModel(); SidebarItem _selectedItem = SidebarItem.events; bool _isExpanded = false; SidebarItem get selectedItem => _selectedItem; bool get isExpanded => _isExpanded; void selectItem(SidebarItem item) { _selectedItem = item; notifyListeners(); } void toggleSidebarExpansion() { _isExpanded = !_isExpanded; notifyListeners(); } void expandSidebar() { if (_isExpanded) return; _isExpanded = true; notifyListeners(); } void collapseSidebar() { if (!_isExpanded) return; _isExpanded = false; notifyListeners(); } }
错误原因
错误核心是:在LayoutBuilder的builder回调(属于Widget构建/布局阶段)中调用了ViewModel的notifyListeners()方法,该方法会触发所有依赖此ViewModel的组件标记为需要重建。而此时Flutter框架正处于构建Widget的过程中,不允许直接触发状态更新通知,因此抛出该异常。
解决方案
将状态更新逻辑延迟到当前帧构建完成后执行,使用WidgetsBinding.instance.addPostFrameCallback:
修改后的RootPage代码:
class RootPage extends StatelessWidget { const RootPage({required this.widget}); final Widget widget; @override Widget build(BuildContext context) { final SidebarViewModel sidebarViewModel = context.read<SidebarViewModel>(); return Scaffold( body: LayoutBuilder( builder: (_, BoxConstraints constraints) { // 延迟到当前帧构建完成后执行状态更新 WidgetsBinding.instance.addPostFrameCallback((_) { constraints.maxWidth > 1100 ? sidebarViewModel.expandSidebar() : sidebarViewModel.collapseSidebar(); }); return Row(children: [const Sidebar(), Expanded(child: widget)]); }, ), ); } }
原理说明
addPostFrameCallback会在当前Widget树构建完成、帧绘制完成后执行传入的回调函数。此时框架已完成当前的build流程,再调用状态更新方法触发组件重建,就不会和正在进行的构建过程冲突,从而避免异常。
内容的提问来源于stack exchange,提问作者Jan Hecker
相关产品推荐
相关产品推荐

