Repository navigation
[cupertino_ui] Fix CupertinoSheetRoute throwing when a drag ends during a pending relayout - #13316
Conversation
…ng a pending relayout The sheet's drag handlers read context.size to convert drag deltas and velocities into fractions of the sheet height. Element.size asserts when the render object is dirty for layout, so a drag that ended while a relayout was pending threw 'Cannot get size from a render object that has been marked dirty for layout'. Read the size from the render box instead, which is the last laid out height and what release builds already used.
There was a problem hiding this comment.
Code Review
This pull request updates the sheet height retrieval in CupertinoSheetRoute to use the render box instead of context.size, preventing assertions when a drag ends during a pending relayout. Corresponding regression tests and a changelog entry are included. The review feedback suggests caching the _sheetHeight value in a local variable within _dragUpdate to avoid redundant render object lookups during drag events.
| void _dragUpdate(double delta) { | ||
| assert(mounted); | ||
| if (_dragGestureController != null) { | ||
| _dragGestureController!.dragUpdate( | ||
| delta / (context.size!.height - (context.size!.height * _kTopGapRatio)), | ||
| delta / (_sheetHeight - (_sheetHeight * _kTopGapRatio)), | ||
| null, | ||
| ); | ||
| } |
There was a problem hiding this comment.
During a drag update, _sheetHeight is accessed twice. Since _sheetHeight performs a tree lookup via context.findRenderObject(), caching this value in a local variable avoids redundant lookups during high-frequency drag events.
| void _dragUpdate(double delta) { | |
| assert(mounted); | |
| if (_dragGestureController != null) { | |
| _dragGestureController!.dragUpdate( | |
| delta / (context.size!.height - (context.size!.height * _kTopGapRatio)), | |
| delta / (_sheetHeight - (_sheetHeight * _kTopGapRatio)), | |
| null, | |
| ); | |
| } | |
| void _dragUpdate(double delta) { | |
| assert(mounted); | |
| if (_dragGestureController != null) { | |
| final double sheetHeight = _sheetHeight; | |
| _dragGestureController!.dragUpdate( | |
| delta / (sheetHeight - (sheetHeight * _kTopGapRatio)), | |
| null, | |
| ); | |
| } | |
| } |
…drag-size-during-layout
dkwingsmt
left a comment
There was a problem hiding this comment.
LGTM except for a nit. Thank you for the fix!
| // Reads the height from the render box instead of context.size, which asserts | ||
| // when the box is dirty for layout. A drag can end while a relayout is | ||
| // pending, and the last laid out height is the one the drag was made against. |
There was a problem hiding this comment.
nit: The verb "assert" is typically followed by the correct behavior, making the sentence a bit awkward to read at first glance.
| // Reads the height from the render box instead of context.size, which asserts | |
| // when the box is dirty for layout. A drag can end while a relayout is | |
| // pending, and the last laid out height is the one the drag was made against. | |
| // Reads the height from the render box instead of context.size, which throws | |
| // in debug mode when the box is dirty for layout. A drag can end while a relayout is | |
| // pending, and the last laid out height is the one the drag was made against. |
| } | ||
|
|
||
| // See _CupertinoDragGestureDetectorState.sheetHeight. | ||
| double get _sheetHeight => (context.findRenderObject()! as RenderBox).size.height; |
There was a problem hiding this comment.
nit: It may help to extract the two methods into a global function. Non-blocking comment. Feel free to decide whether to do it.
There was a problem hiding this comment.
Done in babcf0b: both drag paths now call a top-level _sheetHeightOf(context), and each handler reads it once.
Moves the render box height read into a top-level function used by both sheet drag paths, and rewords its comment to say context.size throws in debug mode.
…er#196381) flutter/packages@08faf64...653628e 2026-11-11 [email protected] Roll Flutter from fffd8c2 to b98afb5 (31 revisions) (flutter/packages#13623) 2026-11-11 [email protected] [material_ui] Document how to programmatically clear DropdownMenu selection (flutter/packages#13266) 2026-11-11 [email protected] [pigeon] Fix basePath resolution when paths share a string prefix with basePath (flutter/packages#13342) 2026-11-11 [email protected] [cupertino_ui] Fix CupertinoSheetRoute throwing when a drag ends during a pending relayout (flutter/packages#13316) 2026-11-11 [email protected] [shared_preferences] Updates Pigeon to 30.0.1 to support multi-engine registration and deregistration. (flutter/packages#13617) 2026-11-11 [email protected] [vector_graphics] Catch any throwable in useHtmlRenderObject() and cache HTML fallback (flutter/packages#13545) 2026-11-11 [email protected] [cupertino_ui] Include selectionHandleColor in NoDefaultCupertinoThemeData equality (flutter/packages#13450) 2026-11-11 [email protected] [flutter_svg] Remove failed async loads from Cache._pending (flutter/packages#13604) 2026-11-11 [email protected] [pigeon] Adds Native Interop FFI codec serialization for NS types (flutter/packages#13616) 2026-11-10 [email protected] [pigeon] Support multi-engine Native Interop registration and instance-aware unregistration (flutter/packages#13576) 2026-11-10 [email protected] [image_picker_ios] Convert the iOS example app and UI tests to Swift (flutter/packages#13570) 2026-11-10 [email protected] [cupertino_ui] Localize CupertinoActionSheet semantics label (flutter/packages#13377) 2026-11-10 [email protected] [camera_android] Fix stopVideoRecording returning JPEG when takePicture runs during recording (flutter/packages#13070) 2026-11-10 [email protected] [google_sign_in_android] Rename keep.xml to google_sign_in_keep.xml (flutter/packages#13475) 2026-11-10 [email protected] [web_benchmarks] Fix benchmark stalling when only repainting views that are dirty (flutter/packages#13089) 2026-11-10 [email protected] Roll Flutter from d463821 to fffd8c2 (21 revisions) (flutter/packages#13614) If this roll has caused a breakage, revert this CL and set the roller to dry run mode using the controls here: https://autoroll.skia.org/r/flutter-packages-flutter-autoroll Please CC [email protected] on the revert to ensure that a human is aware of the problem. To file a bug in Flutter: https://github.com/flutter/flutter/issues/new/choose To report a problem with the AutoRoller itself, please file a bug: https://issues.skia.org/issues/new?component=1389291&template=1850622 Documentation for the AutoRoller is here: https://skia.googlesource.com/buildbot/+doc/main/autoroll/README.md
Dragging a
CupertinoSheetRouteconverts drag deltas and the release velocity into fractions of the sheet's height. Both drag paths read that height fromcontext.size:_CupertinoDragGestureDetectorState.sheetHeightfor regular sheets, and_dragUpdate/_handleDragEndin_CupertinoDraggableScrollableSheetStatefor sheets built withscrollableBuilder.Element.sizeasserts when the render object is dirty for layout, so a drag that ends while a relayout is pending throwsCannot get size from a render object that has been marked dirty for layout.The reporter saw it cascade into_drag == nullasserts inScrollableState, after which the sheet stopped responding. In release the assert is stripped and the read returns the last laid out size.The fix reads the height from the
RenderBoxdirectly. That's the size the sheet was last laid out at, which is the size the drag was made against, and it's what release builds were already using. The MaterialBottomSheetreads its height for drag updates and fling velocity the same way (_childHeight).Tests
Two regression tests, one per drag path. Each starts a partial drag, marks the box the sheet measures as dirty, and releases before the next frame. Both fail on
mainwith the assertion above, and both pass with the fix, with the sheet settling back to where it started. The rest ofsheet_test.dartpasses as well (44 tests in total).One limitation: the reporter hit this on a device when the keyboard was dismissed mid-drag. Changing the view insets mid-drag in a widget test doesn't reproduce it, because the relayout that follows lands before the pointer up. So the tests set up the condition from the reporter's stack trace directly, not the keyboard sequence.
Fixes flutter/flutter#194681
Pre-Review Checklist
[shared_preferences]///).I used AI tooling while working on this, and reviewed and tested all of it myself.
Footnotes
Regular contributors who have demonstrated familiarity with the repository guidelines only need to comment if the PR is not auto-exempted by repo tooling. ↩ ↩2