Skip to content

Conversation

@LipJ01
Copy link

@LipJ01 LipJ01 commented Sep 8, 2025

Summary

  • Defer applying options in didUpdateWidget to the next frame to avoid setState() during build re-entrancy in MapInteractiveViewer.

Problem

  • Under certain rebuild patterns, FlutterMap.didUpdateWidget sets controller.options during the parent’s build. This triggers the controller’s listeners, which call MapInteractiveViewerState.setState() while FlutterMap is still building.
  • This causes the framework assertion:
    • “setState() or markNeedsBuild() called during build”
    • Offending widget: MapInteractiveViewer
    • Caller chain shows FlutterMap._setMapController / didUpdateWidget → MapControllerImpl.options= → MapInteractiveViewerState.onMapStateChange → setState()

Root cause

  • MapControllerImpl.options updates internal state and notifies listeners immediately. When invoked inside didUpdateWidget (i.e., during the parent’s build), the notification re-enters the widget tree and triggers setState() in a descendant before the parent completes its build.

Change

  • Defer the options assignment in didUpdateWidget to post-frame:
diff --git a/lib/src/map/widget.dart b/lib/src/map/widget.dart
@@ void didUpdateWidget(FlutterMap oldWidget) {
-  if (oldWidget.options != widget.options) {
-    _mapController.options = widget.options;
-  }
+  if (oldWidget.options != widget.options) {
+    WidgetsBinding.instance.addPostFrameCallback((_) {
+      if (mounted) _mapController.options = widget.options;
+    });
+  }
  • File changed: lib/src/map/widget.dart
  • No public API changes. Behavior preserved; updates occur end-of-frame instead of mid-build.

Why this fix works

  • Post-frame defer ensures the parent build completes before any listener-driven setState occurs in MapInteractiveViewer, eliminating the build-cycle re-entrancy.

Backwards compatibility

  • Transparent for existing users:
    • Same options are applied; timing is moved by ≤1 frame.
    • No public API changes or breaking behavior.

Verification

  • Reproduced the assertion with a map rebuild pattern where options changed during didUpdateWidget.
  • With this change, no assertion; MapInteractiveViewer updates on the next frame as expected.
  • Manual verification across web and desktop; behavior remains identical aside from timing.

Related work

@JaffaKetchup
Copy link
Member

Hey, thanks for contributing!

Before we review/accept this, it would be great if you could share an MRE of this issue happening without your fix, and file a bug report just to make it easier to track things :)

@JaffaKetchup
Copy link
Member

JaffaKetchup commented Nov 23, 2025

Hey @LipJ01,
We'd really like to fix the bug with this PR, but unfortunately without a way to reproduce the original bug, we can't prove that it's fixed. This is especially important as this is a crucial pathway within the widget setup (which we've messed up many times before).
Would it be possible for you to provide an MRE? Otherwise we will have to close this PR (although of course we'd be open to another in the future :)).

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants