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 :)

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