Repository navigation
Description
Steps to reproduce
- Open a sheet built with
showCupertinoSheetandscrollableBuilder, while the keyboard is up (non-zero view insets), so the sheet is laid out with a shorter height. - Start a vertical drag on the sheet.
- Clear the view insets mid-drag (the keyboard dismissing).
- End the drag in the same frame as the relayout that follows.
The reproduction is from a shipping app that hit this twice on a physical device, both times with the keyboard leaving. I have not reduced it to a standalone test yet, so please treat step 4 as the condition rather than an exact recipe.
Actual results
The drag end throws:
Cannot get size from a render object that has been marked dirty for layout.
_CupertinoDraggableScrollableSheetState._handleDragEnd (cupertino/sheet.dart:1366)
← _CupertinoSheetScrollPosition.goBallistic ← ScrollDragController.end
In debug the throw then leaves the scroll state inconsistent: ScrollableState fails its _drag == null asserts at scrollable.dart:864 and :873, and the session is unrecoverable. In release the assert is stripped, so the read returns the last laid-out size and nothing cascades. The box in the failing frames was BoxConstraints(w=393, h=255.6) on an iPhone 14 Pro: laid out with the keyboard up, about to be re-laid out without it.
Expected results
No assert, and a stable velocity conversion: the sheet should remember the size it was laid out with, the way Scrollable keeps its metrics, instead of reading context.size from a gesture callback.
Analysis
_CupertinoDraggableScrollableSheetState reads context.size!.height in both _dragUpdate (cupertino/sheet.dart:1357) and _handleDragEnd (cupertino/sheet.dart:1366), and _CupertinoDragGestureDetectorState.sheetHeight does the same (:945, read from _handleDragUpdate and _handleDragEnd at :1002). context.size is Element.size, which throws when the render object is dirty for layout (framework.dart:5030), and the box it resolves to is the scrollableBuilder's root. Any relayout landing in the drag's last frame leaves that box dirty, so the read throws.
For contrast, the Material route reads renderBox.size at the same point in its own drag handling and has no such check.
Code sample
Minimal shape of the failing setup, rather than a runnable file:
showCupertinoSheet<void>(
context: context,
scrollableBuilder: (context, controller) => ListView(
controller: controller,
children: const <Widget>[SizedBox(height: 48, child: Text('row'))],
),
);
// With the keyboard up: press, drag, dismiss the keyboard mid-drag, release.Flutter version
Flutter 3.47.7, stable, revision abaf9c5, engine d8a2d6c768c334d53e16277f837970ffa1e2de81.
Target: iPhone 14 Pro, iOS 27, debug build (the assert is debug-only).
Activity
theprantadutta commentedon Oct 19, 2026
I've opened flutter/packages#13316 with a fix. Both drag paths now read the height from the sheet's
RenderBoxinstead ofcontext.size. That's the last laid out size, which matches what release builds were already using. There's a regression test for each path, and both fail with this exact assertion without the fix.I couldn't reproduce the keyboard sequence itself in a widget test, so the tests set up the condition from your stack trace directly. If you're able to try the branch in your app, that would confirm it covers your case.
mbcorona commentedon Oct 19, 2026
Hi @Shoezmann, thanks for the report, and @theprantadutta for the PR. The context.size
reads are there on master, this checks out. Routing to team-design.
[cupertino_ui] Fix CupertinoSheetRoute throwing when a drag ends duri…