Minimal reproduction for flutter/flutter#151536:
StateError: Bad state: RenderBox was not laid out thrown from
MultiSelectableSelectionContainerDelegate._compareScreenOrder when
_RenderTheater skips laying out an obscured OverlayEntry while its
SelectionContainerDelegate keeps processing registered selectables.
- Bug: open, P2, unfixed on Flutter 3.41.6 stable and current master.
- Mechanism:
_RenderTheaterskips layout of non-topmostOverlayEntry; their registered selectables have nosize;_flushAdditionssort crashes. - Three reproductions of the same root cause in different code paths:
A (
_getBoundingBoxviapaintBounds), B (_SelectableTextContainerDelegateviagetBoxesForSelection), C (getTransformToviaRenderFractionalTranslation.applyPaintTransform). - Proof-of-concept branch:
fix/non-opaque-selectable-page— wrapping the top Page inopaque: falseprevents_RenderTheaterfrom skipping the parent's layout, proving the mechanism. Not production-ready for full-screen routes (causes visual overlap). - Framework fix: flutter/flutter#184900
(open) — try/catch guard in both
_compareScreenOrderoverrides (selectable_region.dartandtext.dart), catching the crash at its exact throw site with zero behavioral regressions. Route-levelSelectionContainer.disabledwrappers were invalidated (nestedSelectionAreacreates its own registrar that shadows the disabled scope).
git clone https://github.com/davidmigloz/flutter_selection_crash.git
cd flutter_selection_crash
flutter run -d chrome --wasmClick into any of the three approach cards. Browser console shows the crash
immediately — no interaction required. Each approach hits a different frame
in the same _compareScreenOrder method. See
investigation/2026-04-10_verification.md
for the full stack traces.
git checkout fix/non-opaque-selectable-page
flutter run -d chrome --wasmApproach C now uses a local _NonOpaquePage extends Page<void> that creates
a PageRouteBuilder(opaque: false, ...). The bottom route stays laid out,
the comparator sees valid sizes/transforms, no crash.
Caveat: opaque: false causes the parent route to be painted behind
the child (both Scaffolds render), creating visual text/widget overlap on
full-screen routes. This branch proves the crash mechanism but is NOT
suitable for production use.
The fix applied in production apps uses a route-aware SelectionArea
toggle: when the route is current, child is wrapped in SelectionArea
(text selectable); when another route covers it, child is wrapped in
SelectionContainer.disabled (selectables deregister). Uses
ModalRoute.of(context)?.isCurrent + GlobalKey + KeyedSubtree.
This works at the app level because the app controls where SelectionArea
is placed. It cannot be applied at the framework level because
SelectionArea/SelectableRegion create their own registrar that shadows
any outer disabled scope — see INVESTIGATION.md for the
detailed analysis of why route-level SelectionContainer.disabled wrappers
don't work.
flutter/flutter#184900
— guards both _compareScreenOrder overrides (selectable_region.dart
and text.dart) with a try/catch for StateError/AssertionError when
accessing geometry on unlaid-out RenderBoxes. Minimal, surgical, zero
behavioral regressions. Covered by two widget-level regression guards plus
four mock-based red/green tests (one StateError + one AssertionError
case per guarded method).
See INVESTIGATION.md for the full proposal, why route-level
SelectionContainer.disabled wrappers don't work at the framework level, and
known limitations (dead zones in #182573 unchanged).
INVESTIGATION.md— full investigation summary, the framework fix, and why route-levelSelectionContainer.disabledwrappers don't work. Also documents the sibling bug inPlatformSelectableRegionContextMenu._activeClient(web right-click during route transitions) that #184900 does not cover.investigation/2026-04-10_verification.md— stack traces for all three approaches and theopaque: falseisolation test.
- flutter/flutter#151536 — canonical issue, open
- flutter/flutter#184900 — framework fix PR (open): try/catch guard in both
_compareScreenOrderoverrides - flutter/flutter#189575 — sibling issue (open): web right-click
_activeClientrace, three lifecycle defects - flutter/flutter#189587 — sibling fix PR (open): corrects the three lifecycle defects behind #189575
- flutter/flutter#151536 (comment 4225683042) — upstream analysis comment: repro + mechanism + initial fix direction (later revised)
- flutter/flutter#151536 (comment 4227118235) — upstream follow-up: pivot from
_ModalScopeStatewrapper to_compareScreenOrderguard, links the framework PR - flutter/packages#11062 —
go_routerPR (closed, unmerged, superseded): app-level route-awareSelectionAreatoggle; reviewed by @Renzo-Olivares - flutter/flutter#182573 — sibling dead-zone variant (not fixed by the framework PR)
- flutter/flutter#117527 — related, recently resolved (different code path)
- flutter/flutter#119776 — related, resolved (different code path)
- flutter/flutter#157996 — Gustl22's incomplete draft (prior art)
- flutter/flutter#158918 — Gustl22's diagnostic draft (prior art)
| Command | Reproduces? |
|---|---|
flutter run -d chrome (DDC debug) |
Yes |
flutter run -d chrome --wasm (dart2wasm debug) |
Yes |
flutter run -d chrome --release --wasm (dart2wasm release) |
Yes |
flutter run -d chrome --release (dart2js release) |
Yes |
flutter test (VM) |
No (synchronous pump) |
This bug has been observed in production Flutter web apps: 640+ Sentry events
across 5+ users. Routes affected: any child route stacked on a parent that
wraps its content in SelectionArea. Workaround applied: route-aware
SelectionArea toggle using ModalRoute.isCurrent +
SelectionContainer.disabled (see "Production workaround" above).
On 2026-04-22, with _RouteAwareSelectionArea and Flutter 3.41.6 in place,
a distinct crash surfaced in production:
Assertion failed: .../widgets/selection_container.dart:303:7
_selectionContainerContext?.findRenderObject() != null
"getTransformTo cannot be called before SelectionContainer is laid out."
at package:flutter/src/widgets/_platform_selectable_region_context_menu_web.dart:96:41
at package:flutter/src/widgets/_platform_selectable_region_context_menu_web.dart:136:11
Same underlying pattern ("geometry read on an unlaid-out / disposed
SelectionContainer"), different code path: the web mousedown JS
listener calls client.getTransformTo(null) on a stale static
_activeClient during a route transition. Neither the
_RouteAwareSelectionArea toggle nor framework PR #184900 cover this
path.
Filed upstream as flutter/flutter#189575.
Root-causing it there found three concrete lifecycle defects in
PlatformSelectableRegionContextMenu's attach/detach pair and
SelectableRegionState's focus/dispose handling — not just an unguarded
read. Fix PR flutter/flutter#189587
corrects the three defects directly and is open, independent of #184900.
See the INVESTIGATION.md sibling-bug section for the full root-cause breakdown, the fix PR's approach, and why a narrower geometry-read guard (our original proposal here) would have been insufficient.