Skip to content

Exception thrown when constraints change during rebuild #2162

Open

Description

@jonl-percsolutions-com

What is the bug?

An exception can occur in MapInteractiveViewer.onMapStateChange if the constraints changed during a rebuild. We have seen this in 8.1.1 - 8.2.2.

The following assertion was thrown while dispatching notifications for MapControllerImpl:
setState() or markNeedsBuild() called during build.

This MapInteractiveViewer 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: MapInteractiveViewer
  dependencies: [MediaQuery]
  state: MapInteractiveViewerState#aa93f(tickers: tracking 18 tickers)
The widget which was currently being built when the offending call was made was: LayoutBuilder
  renderObject: _RenderLayoutBuilder#d5341 relayoutBoundary=up9 NEEDS-LAYOUT NEEDS-PAINT
When the exception was thrown, this was the stack: 
#0      Element.markNeedsBuild.<anonymous closure> (package:flutter/src/widgets/framework.dart:5289:9)
#1      Element.markNeedsBuild (package:flutter/src/widgets/framework.dart:5301:6)
#2      State.setState (package:flutter/src/widgets/framework.dart:1227:15)
#3      MapInteractiveViewerState.onMapStateChange (package:flutter_map/src/gestures/map_interactive_viewer.dart:182:5)
#4      ChangeNotifier.notifyListeners (package:flutter/src/foundation/change_notifier.dart:439:24)
#5      ValueNotifier.value= (package:flutter/src/foundation/change_notifier.dart:564:5)
#6      MapControllerImpl.value= (package:flutter_map/src/map/controller/map_controller_impl.dart:81:49)
#7      MapControllerImpl.setNonRotatedSizeWithoutEmittingEvent (package:flutter_map/src/map/controller/map_controller_impl.dart:321:7)
#8      _FlutterMapStateContainer._updateAndEmitSizeIfConstraintsChanged (package:flutter_map/src/map/widget.dart:135:24)
#9      _FlutterMapStateContainer.build.<anonymous closure> (package:flutter_map/src/map/widget.dart:98:11)
#10     _LayoutBuilderElement._rebuildWithConstraints.updateChildCallback (package:flutter/src/widgets/layout_builder.dart:201:77)
#11     BuildOwner.buildScope (package:flutter/src/widgets/framework.dart:3056:19)
#12     _LayoutBuilderElement._rebuildWithConstraints (package:flutter/src/widgets/layout_builder.dart:240:12)
#13     RenderObject.invokeLayoutCallback.<anonymous closure> (package:flutter/src/rendering/object.dart:2827:17)
#14     PipelineOwner._enableMutationsToDirtySubtrees (package:flutter/src/rendering/object.dart:1161:15)
#15     RenderObject.invokeLayoutCallback (package:flutter/src/rendering/object.dart:2826:14)
#16     RenderConstrainedLayoutBuilder.rebuildIfNecessary (package:flutter/src/widgets/layout_builder.dart:293:5)
#17     _RenderLayoutBuilder.performLayout (package:flutter/src/widgets/layout_builder.dart:390:5)
#18     RenderObject.layout (package:flutter/src/rendering/object.dart:2715:7)
#19     RenderProxyBoxMixin.performLayout (package:flutter/src/rendering/proxy_box.dart:115:18)
#20     RenderObject.layout (package:flutter/src/rendering/object.dart:2715:7)
#21     RenderPadding.performLayout (package:flutter/src/rendering/shifted_box.dart:243:12)
#22     RenderObject.layout (package:flutter/src/rendering/object.dart:2715:7)
#23     RenderProxyBoxMixin.performLayout (package:flutter/src/rendering/proxy_box.dart:115:18)
#24     _RenderCustomClip.performLayout (package:flutter/src/rendering/proxy_box.dart:1483:11)
#25     RenderObject.layout (package:flutter/src/rendering/object.dart:2715:7)
#26     RenderProxyBoxMixin.performLayout (package:flutter/src/rendering/proxy_box.dart:115:18)
#27     RenderObject.layout (package:flutter/src/rendering/object.dart:2715:7)
#28     ChildLayoutHelper.layoutChild (package:flutter/src/rendering/layout_helper.dart:62:11)
#29     RenderStack._computeSize (package:flutter/src/rendering/stack.dart:646:43)
#30     RenderStack.performLayout (package:flutter/src/rendering/stack.dart:673:12)
#31     RenderObject.layout (package:flutter/src/rendering/object.dart:2715:7)
#32     RenderConstrainedBox.performLayout (package:flutter/src/rendering/proxy_box.dart:293:14)
#33     RenderObject.layout (package:flutter/src/rendering/object.dart:2715:7)
#34     RenderPadding.performLayout (package:flutter/src/rendering/shifted_box.dart:243:12)
#35     RenderObject.layout (package:flutter/src/rendering/object.dart:2715:7)
#36     RenderProxyBoxMixin.performLayout (package:flutter/src/rendering/proxy_box.dart:115:18)
#37     RenderObject.layout (package:flutter/src/rendering/object.dart:2715:7)
#38     ChildLayoutHelper.layoutChild (package:flutter/src/rendering/layout_helper.dart:62:11)
#39     RenderFlex._computeSizes (package:flutter/src/rendering/flex.dart:1161:28)
#40     RenderFlex.performLayout (package:flutter/src/rendering/flex.dart:1255:32)
#41     RenderObject.layout (package:flutter/src/rendering/object.dart:2715:7)
#42     _RenderLayoutBuilder.performLayout (package:flutter/src/widgets/layout_builder.dart:392:14)
#43     RenderObject._layoutWithoutResize (package:flutter/src/rendering/object.dart:2548:7)
#44     PipelineOwner.flushLayout (package:flutter/src/rendering/object.dart:1112:18)
#45     PipelineOwner.flushLayout (package:flutter/src/rendering/object.dart:1125:15)
#46     RendererBinding.drawFrame (package:flutter/src/rendering/binding.dart:616:23)
#47     WidgetsBinding.drawFrame (package:flutter/src/widgets/binding.dart:1231:13)
#48     RendererBinding._handlePersistentFrameCallback (package:flutter/src/rendering/binding.dart:482:5)
#49     SchedulerBinding._invokeFrameCallback (package:flutter/src/scheduler/binding.dart:1442:15)
#50     SchedulerBinding.handleDrawFrame (package:flutter/src/scheduler/binding.dart:1355:9)
#51     SchedulerBinding._handleDrawFrame (package:flutter/src/scheduler/binding.dart:1208:5)
#52     _invoke (dart:ui/hooks.dart:316:13)
#53     PlatformDispatcher._drawFrame (dart:ui/platform_dispatcher.dart:428:5)
#54     _drawFrame (dart:ui/hooks.dart:288:31)

How can we reproduce it?

It is intermittent to reproduce, but the exception is clear.

If _FlutterMapStateContainer._updateAndEmitSizeIfConstraintsChanged actually detects a change, and the MapInteractiveViewer.onMapStateChange is attached as a listener, then this will cause a "setState" call to be invoked during the build process.

Do you have a potential solution?

Best solution is likely to modify the behavior of _FlutterMapStateContainer._updateAndEmitSizeIfConstraintsChanged` so the whole check occurs in a postFrameCallback

May also need to modify the behavior MapInteractiveViewerState.onMapStateChange so that setState is called within a post frame callback.

Activity

  1. jonl-percsolutions-com commented on Nov 11, 2025

    @jonl-percsolutions-com
    Author

    My interim solution until this is resolved. not sure what other issues may arise from this.

    class _MyMapControllerImpl extends MapControllerImpl {
    
      bool _initialChangeRotateDone = false;
    
      @override
      bool setNonRotatedSizeWithoutEmittingEvent(Size nonRotatedSize) {
        if (!_initialChangeRotateDone) {
          _initialChangeRotateDone = true;
          return super.setNonRotatedSizeWithoutEmittingEvent(nonRotatedSize);
        }
        if (nonRotatedSize != MapCamera.kImpossibleSize &&
            nonRotatedSize != camera.nonRotatedSize) {
          WidgetsBinding.instance.addPostFrameCallback((_) {
            super.setNonRotatedSizeWithoutEmittingEvent(nonRotatedSize);
          },);
    
          return true;
        }
    
        return false;
      }
    
    }
  2. JaffaKetchup commented on Nov 11, 2025

    @JaffaKetchup
    Member

    Hey @jonl-percsolutions-com ,

    Thanks for the report. That workaround does look like something that we could implement. However we need to be 100% sure how the issue exists first so we can test repeatedly whether it remains fixed. Is there any way you could make a small MRE which at least triggers the issue most of the time?

  3. jonl-percsolutions-com commented on Nov 19, 2025

    @jonl-percsolutions-com
    Author

    @JaffaKetchup,

    This is an intermittent issue..

    All you have to do to confirm the possible issue is trace the log. If MapInteractiveViewerState.onMapStateChange is called during a build process, there will be an exception because it calls setState(), and set state shouldn't be called during a build process. I think newer versions of flutter are supposedly handling these a bit better I am using flutter 3.29.1 currently, which maybe if you run with that it will be more re-producible.

    This is the offending line of code in MapInteractiveViewerState

      /// Rebuilds the map widget
      void onMapStateChange() {
        _updateKeyboardPanAnimationZoomLevel();
        setState(() {});
      }
  4. JaffaKetchup commented on Nov 24, 2025

    @JaffaKetchup
    Member

    Could you please explain how you are "changing the constraints during a rebuild"? I can see that there might be a bug, but I need to reproduce it to prove that any fix has worked.

  5. github-actions commented on Dec 16, 2025

    @github-actions

    This issue requires additional information in order to be resolved. However, there hasn't been any response within the last 3 weeks.
    If this issue still persists, please provide the requested information. This issue will be automatically closed in one week if there is no response.

  6. added
    staleThis issue has not been received updates for a long time.
    on Dec 16, 2025
  7. jonl-percsolutions-com commented on Dec 16, 2025

    @jonl-percsolutions-com
    Author

    Could you please explain how you are "changing the constraints during a rebuild"? I can see that there might be a bug, but I need to reproduce it to prove that any fix has worked.

    Sorry for the delay in response. End of the year and all that.

    My reference to "constraints changed" is basically a reference to the logic /code that was causing the issue

    in widget.dart

     void _updateAndEmitSizeIfConstraintsChanged(BoxConstraints constraints) {
        final nonRotatedSize = Size(
          constraints.maxWidth,
          constraints.maxHeight,
        );
        final oldCamera = _mapController.camera;
        if (_mapController.setNonRotatedSizeWithoutEmittingEvent(nonRotatedSize)) {
          final newMapCamera = _mapController.camera;
    
          // Avoid emitting the event during build otherwise if the user calls
          // setState in the onMapEvent callback it will throw.
          WidgetsBinding.instance.addPostFrameCallback((_) {
            if (mounted) {
              _mapController.nonRotatedSizeChange(
                MapEventSource.nonRotatedSizeChange,
                oldCamera,
                newMapCamera,
              );
    
              _applyInitialCameraFit(constraints);
            }
          });
        }
      }
    

    in map_controller_impl.dart

      bool setNonRotatedSizeWithoutEmittingEvent(
        Size nonRotatedSize,
      ) {
        if (nonRotatedSize != MapCamera.kImpossibleSize &&
            nonRotatedSize != camera.nonRotatedSize) {
          value = value.withMapCamera(camera.withNonRotatedSize(nonRotatedSize));
          return true;
        }
    
        return false;
      }
    

    If this actually gets triggered a second time, which could be due to a screen resize where the map view is resized or we have a sidepanel that opens and closes resizing the map view, then the value change here triggers a the notifier, which, because there is an attached event in the MapInteractiveViewerState on the MapControllerIml, MapInteractiveViewerState.onMapStateChange, calls setState which triggers the exception.

    MapInteractiveViewState

      /// Rebuilds the map widget
      void onMapStateChange() {
        _updateKeyboardPanAnimationZoomLevel();
        setState(() {});
      }

    So it occurs, I believe, because

    1. We are managing the MapController ourselves and passing it down to FlutterMap
    2. We resize the map widget on the fly due to other elements on the screen
    3. We use a method of restarting the application at runtime in order to rebuild the whole widget tree.

    I haven't been able to reliably reproduce the exception enough to create an example.

  8. changed the title [-]Exception on build if constraints changed.[/-] [+]Exception thrown when constraints change during rebuild[/+] on Apr 13, 2026
  9. JaffaKetchup commented on Apr 13, 2026

    @JaffaKetchup
    Member

    I haven't seen this issue myself, and without an MRE, it will be difficult to repro since I would guess it's difficult to trigger, but I appreciate the effort you've put into tracking it down, so I'll leave this own and give it a priority. It looks like someone else (#2193) has run into this as well now, also with no MRE.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    P: 2 (soon™?)needs triageThis new bug report needs reproducing and prioritizing

    Type

    Fields

    Priority

    None yet

    Effort

    None yet

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions