Repository navigation
Description
Use case
Apple announced iPhone Duo on September 9, 2026 — the first foldable iPhone, with a 5.4" outer display and a 7.6" inner display that curves through the centre when partially folded. It ships October 23, 2026.
Flutter apps run on it today, but they have no fold awareness at all:
DisplayFeatureis documented as "populated only on Android." On iPhone Duo,MediaQuery.displayFeaturesreturns an empty list no matter how folded the device is.dual_screen(TwoPane,hingeAngleEvents) is Android-only in practice and was last published roughly three years ago.
So a Flutter app cannot tell that content or a touch target has landed in the fold — where, on this device, it becomes noticeably harder to read and to tap.
Some of the impact is already solvable in pure Dart, and I don't think Flutter needs to do anything about that part: resizing is handled by LayoutBuilder, and the asymmetric safe area (iOS moves its system controls to one side on this device) is correctly reported through MediaQuery.paddingOf per edge. That covers a good portion of a decent port.
What is not reachable from Dart is the fold itself, and the under-display camera region.
Alternatives considered. Each app can write its own platform channel to the new iOS APIs. That works — but it means every foldable-aware Flutter app reimplements the same bridge, and none of them feed MediaQuery.displayFeatures, so no existing display-feature-aware widget or package benefits. Given DisplayFeature already exists as the platform-neutral abstraction, populating it on iOS seems strictly better than N private channels.
Related: #189554 proposes built-in split-view/dual-pane support for large screens and foldables. This issue is narrower — just getting the platform data into dart:ui.
Proposal
Populate DisplayFeature on iOS from the reserved-region APIs introduced in iOS 27.1, in the Darwin embedder (engine/src/flutter/shell/platform/darwin/ios/), plumbed through ViewportMetrics the same way the Android embedder does it from Jetpack WindowManager.
iOS 27.1 adds reservedRegions(kind:) on UIView, returning regions with a frame, a kind, and an active/inactive state:
.division— an area that splits a larger area into smaller ones. The fold is a division region. It is active only while the device is folded; flat, it is inactive with zero width..occlusion— an area that covers part of the view without splitting it. The under-display FaceTime camera on the inner display is an occlusion region, active only while that camera is in use.- A query option
includeInactivereturns regions that exist but are not currently active.
A mapping onto the existing model that I think holds up:
| iOS 27.1 reserved region | DisplayFeatureType |
Notes |
|---|---|---|
.division (the fold) |
fold |
Flutter defines fold as not obstructing the display and hinge as obstructing. iPhone Duo's inner display is a single continuous panel that curves — there is no gap and no second panel — so fold is the accurate value and hinge would be misleading. |
.occlusion (under-display camera) |
cutout |
Occludes without dividing, which matches cutout semantics. |
| iOS region state | DisplayFeatureState |
|---|---|
| active | postureHalfOpened |
| inactive (zero width when flat) | postureFlat |
bounds maps directly from the region's frame, converted to logical pixels.
One open design question
Flutter has no equivalent of includeInactive, and I think that gap is worth deciding on deliberately rather than by omission.
On iOS you can ask "does this device have a fold, even though it is flat right now?" That is genuinely useful for stable layout decisions — Apple's guidance gives the example of always preferring an even number of grid columns on a device that has a fold, so the layout does not reshuffle every time the user bends it.
Flutter's model cannot express that: an inactive feature simply is not in the list, so displayFeatures is empty on a flat iPhone Duo and equally empty on an iPhone 18. An app that wants to lay out stably across the fold cannot distinguish them.
Possible directions, roughly in order of invasiveness:
- Report the inactive fold as a
DisplayFeaturewithstate: postureFlatand zero-width bounds — arguably already consistent withpostureFlatexisting at all, and requires no API change. - Add an opt-in accessor for inactive features.
- Leave it out for now, and document that
displayFeaturesreflects only currently-active features.
Option 1 looks cheapest and most useful to me, but it is a behavioural decision for the team — it would mean a flat foldable reports a feature where today it would report none, and that could surprise Android callers.
Caveat on my end
I have not been able to verify these iOS API signatures against the SDK. iPhone Duo was announced on September 9 and Xcode 27.1 was still rolling out at the time of writing, so everything above comes from Apple's Tech Talk sessions rather than shipped headers. I would treat the names as the shape of the API and confirm against the SDK before implementing.
I am filing this as a proposal rather than a PR for that reason — I cannot build or test the engine change, so I did not want to submit code I could not verify. Happy to help with documentation or follow-up detail if that is useful.
Primary sources (Apple):
- Get ready for iPhone Duo
- Strike a pose with adaptive layouts on iPhone Duo — reserved regions, division vs occlusion,
includeInactive - Prepare your app for iPhone Duo — asymmetric safe areas, size classes
- Leverage multiple displays and scenes on iPhone Duo — hinge angle (
UIHingeInteraction), out of scope here but related - Apple unveils iPhone Duo — device specifications
Metadata
Metadata
Assignees
Labels
Type
Projects
- StatusShow more project fieldsNot started
Activity
diegolopezrm commentedon Sep 10, 2026
Useful proposal, and the caveat about not verifying against the SDK is the right call — I checked and none of
UIHingeInteraction,reservedRegion,includeInactiveorUIFoldexist in the iOS 26.1 SDK's UIKit headers, so those names can't be confirmed until the 27.x toolchain is out.I did measure the other half of it: what the framework does once
displayFeaturesis non-empty on iOS. Three things came out of it, one of which is a bug in the mapping table.The open question answers itself, but not the way it's framed. Option 1 — inactive fold as zero-width bounds plus
postureFlat— is behaviourally inert.DisplayFeatureSubScreen.avoidBoundsreturns empty and anAlertDialogstays full width atRect.fromLTRB(0, 0, 800, 900). So reporting an inactive fold is safe, and the "could surprise Android callers" worry doesn't materialise.The decision that does change behaviour isn't active-vs-inactive. It's whether the embedder reports non-zero
.divisionbounds at all, because the predicate is:With a 40pt fold at the centre of an 800x900 view:
Eleven files under
packages/flutter/lib/src/inherit that one predicate, including a hand-duplicated copy incupertino/menu_anchor.dart. So the day iOS reports a division, every dialog, sheet and picker on the 7.6" inner display gets confined to one ~47% half, withanchorPointas the only escape hatch. That seems worth deciding on deliberately rather than discovering.Worth flagging the degenerate case too: zero-width bounds with
postureHalfOpenedpasses the predicate and yieldsavoidBoundsofRect.fromLTRB(400, 0, 400, 900)— a 400pt dialog and a silent half-screen split with no visible fold. That's the trap if an embedder reasons "device is folded, thereforepostureHalfOpened" while Apple reports the flat division as zero-width.The mapping table as written would assert-crash. It maps
.occlusiontoDisplayFeatureType.cutoutwhile applying the active ->postureHalfOpenedrule, but:A cutout has to carry
DisplayFeatureState.unknown, so occlusions need to skip the state rule.One semantic note. Flutter's split model comes from Android hinges, where two panels genuinely face different directions and treating them as separate sub-screens is right. The Duo's inner display is one continuous curved panel, and Apple's guidance is about not placing content in the division rather than about treating the halves as separate screens. Those aren't the same thing, and
DisplayFeatureSubScreenimplements the first one.Numbers above are from a small widget-test harness against framework master — happy to push it somewhere if it's useful for whoever picks this up, and to re-run it once 27.1 is out and the real reserved-region values are known.
[-]Proposal: populate MediaQuery.displayFeatures on iOS for iPhone Duo (foldable)[/-][+][iPhone Duo] Proposal: populate MediaQuery.displayFeatures on iOS for iPhone Duo (foldable)[/+]hellohuanlin commentedon Sep 19, 2026
The reserved region can also be active/inactive for front camera of inner display, which has nothing to do with
DisplayFeatureState. ReusingDisplayFeatureState(folded / open / half open) for a completely separate concept (active / inactive) can be confusing. We will need to update the API to include this info.hellohuanlin commentedon Sep 20, 2026
Another issue is how to tell if reserved region has changed for front camera of inner display (turn it on/off)
hellohuanlin commentedon Sep 20, 2026
Also an inactive region should still report a non-zero size, so that apps will know how to prepare for it. Passing an inactive region with zero frame would unlikely be any useful.
berkaycatak commentedon Sep 20, 2026
I opened #193025 for this. It populates
displayFeatureson iOS fromUIHingeInteractionand the reserved regions, and is ready for review.Until it lands, the
foldablepackage exposes the same data through a platform channel, plus the hinge angle and posture, which are outside theDisplayFeaturemodel.diegolopezrm commentedon Sep 21, 2026
On telling when a reserved region changes: I couldn't find an API for it either. Apple is running a written UIKit Q&A for iPhone Duo on the 23rd and that question is already on my list — I'll bring back whatever they answer.
On active/inactive: agreed it doesn't belong in
DisplayFeatureState. Worth adding that the two aren't even in step — in #193025isActivelags the hinge status, so a division can still read active in the update that reports the device fully open. Whatever ends up carrying active/inactive has to tolerate that skew instead of assuming the two agree.iPhone Duo phase C: the two-pane split follows the fold
JamesFieldist commentedon Nov 11, 2026
Tracking this from Fieldist for iPhone Duo unfold detection (https://github.com/Fieldist/fieldist/issues/2941).
MediaQuery.displayFeaturesis empty on iOS today, so we match the inner Duo logical size (669×951) as a stand-in. Flutter 3.50 (#195199 / #195690) can change that size. We will drop the size branch and unify on fold/hinge features when this issue and PR #193025 land.