Skip to content

Flutter on iPhone Duo

The abstraction already exists from Android foldables. Whether the iOS embedder populates it for iPhone Duo is unconfirmed.

iPhone Duo goes on sale 23 October 2026, running iOS 27.1. Anything you fix before then, your users never see broken. Start with what breaks first.

Flutter iPhone Duo checklist

Most Flutter apps adapt well to iPhone Duo, because Flutter’s layout system is resolution-independent. Whether yours does comes down to the patterns below, and each blocker is drawn further down the page.

Flutter on iPhone Duo: what works, what breaks

  • WorksLayoutBuilder and flexible widgetsResolution-independent; reflows at any size.
  • WorksMediaQuery.sizeOf(context) in buildRebuilds only when the size changes.
  • BreaksScreen size cached in StateStale after the first fold, rotation or Split View resize.
  • BreaksSystemChrome.setPreferredOrientationsThe inner display ignores supported orientations.
  • BreaksOrientationBuilder as a layout signalReports shape, not room. Branch on width.
  • WorksMediaQuery.paddingOf(context)Exposes left and right separately.
  • BreaksEdgeInsets.symmetric(horizontal: …)Asserts a symmetry the insets no longer have.
  • WorksA width >= 600 breakpointYour tablet layout now appears on an iPhone — usually correctly.
  • BreaksPausing on AppLifecycleState.inactiveIn Split View a visible app can be inactive.
  • Looks datedAspectRatio(16 / 9) filling the screenLetterboxes heavily on a 1.42 display.
  • UnverifieddisplayFeatures and TwoPane on iOSMay be empty on iPhone Duo. Log it on a real build.
  • Looks datedFlutter-drawn tab bars (CupertinoTabBar, NavigationBar)Stay where your layout puts them while system bars move to the side.
  • UnverifiedThe camera plugin across a foldDepends on the plugin adopting Apple’s camera coordinators. Test it.
  • Needs nativeArrangementView, vertical barsNo Dart API; needs a platform channel.

Flutter already has the foldable abstraction

Flutter is in an unusual position. It already has the right abstraction for foldables, built for Android devices years ago, but there is no confirmation yet that it is wired up on iOS for iPhone Duo.

MediaQuery.displayFeatures reports hinges and cutouts, with bounds and state, and the TwoPane widget was built on top of it for Android foldables. It maps almost exactly onto Apple’s model: a hinge that splits the display is Apple’s division region, and a cutout that covers part of it is an occlusion region.

The layout system underneath is resolution-independent, so most screens need no fold-specific code at all. What changes is the range of widths they receive. The closed outer display is 466 points wide. The open inner display is 626 points wide held upright and 890 held sideways, and Split View can hand you anything in between.

iPhone Duo screen widths against a 600 point breakpointThree iPhone Duo screens at the same scale, each crossed by a dashed line at 600 points. The closed outer display is 466 points wide, so the line falls beyond its edge and the layout is one column. The open inner display is 626 points wide held upright and 890 held sideways; the line falls inside both, so both get a sidebar and a detail view.600 pt breakpoint466 ptclosed1 column626 ptopen2 columns890 ptopen, rotated2 columns
Drawn to scale. One check on MediaQuery.sizeOf(context).width, at 600 points, gives one column when closed and two when open, in either orientation. It also drops back to one column when Split View narrows the window, with no extra code. Apple’s advice for native apps is to target size classes rather than breakpoints; this framework has no size classes, so a single width check is our closest equivalent.

Most Flutter apps already have a tablet breakpoint near 600 logical pixels. It will now fire on an iPhone, and the layout behind it is very likely the right one. Just make sure it is driven by width, not by platform.

The blockers, drawn on iPhone Duo

Each figure shows the same screen twice on the inner display, at its real proportions. On the left is the pattern most apps ship today, and on the right is the same screen after the fix. Below each drawing is the Dart that produces it.

Built against an older SDK

This one is not a crash. flutter build ios uses whichever Xcode is selected. An app built without the iOS 27 SDK runs at a familiar size and aspect ratio, with the rest of the display unused. The iOS 27.1 SDK is what lets it reach the screen edge.

Older SDK: runs, but letterboxed versus iOS 27.1 SDK: reaches the edgeTwo iPhone Duo inner displays, 626 by 890 points. Left: an app built without the iOS 27 SDK runs in a 402-point-wide phone-shaped window with black bars either side. Right: the same app built with the iOS 27.1 SDK fills the display edge to edge with a two-column layout.402 pt626 pt

Left: Older SDK: runs, but letterboxed

xcrun --sdk iphoneos --show-sdk-version
# 26.x → a familiar size and ratio

Right: iOS 27.1 SDK: reaches the edge

xcrun --sdk iphoneos --show-sdk-version
# 27.1 → extends to the screen edge
The letterboxed window on the left is illustrative: Apple describes it only as "a familiar size and aspect ratio".How to fix it — Older SDK: runs, but letterboxed

Screen size cached in State

A size read once and kept in a field goes stale the first time the device folds, rotates or enters Split View. Launch on the closed display, unfold, and everything built from that number stays sized for 466 points on a 626-point screen. Read MediaQuery.sizeOf in build. It rebuilds only when the size changes, which matters on a device where the size changes often.

Width captured while folded versus Width read on every buildTwo iPhone Duo inner displays, 626 points wide. Left: cards sized from a width read while the device was folded, 466 points, stay 434 points wide after unfolding and leave an empty band down the right side. Right: cards sized from the live width fill 594 points.434 pt594 pt

Left: Width captured while folded

double? _width;

@override
void didChangeDependencies() {
  super.didChangeDependencies();
  // Captured once, never updated
  _width ??=
      MediaQuery.sizeOf(context).width;
}

Right: Width read on every build

@override
Widget build(BuildContext context) {
  final w =
      MediaQuery.sizeOf(context).width;
  return SizedBox(
    width: w - 32,
    child: card,
  );
}
Drawn to iPhone Duo’s point dimensions.How to fix it — Width captured while folded

setPreferredOrientations ignored on the inner display

Apple states the inner display does not honour supported interface orientations, so a portrait lock silently stops applying. OrientationBuilder does not help either: it reports the shape of the screen, and at a ratio of 1.42 the shape tells you little. Branch on width instead.

Portrait lock, ignored versus Layout chosen by widthTwo iPhone Duo inner displays held landscape, 890 by 626 points. Left: a portrait-locked layout is shown anyway, and its full-width hero image runs off the bottom of the screen. Right: a layout chosen by width shows a sidebar beside the detail view.890 × 626 pt890 × 626 pt

Left: Portrait lock, ignored

SystemChrome.setPreferredOrientations([
  DeviceOrientation.portraitUp,
]);

Right: Layout chosen by width

final width =
    MediaQuery.sizeOf(context).width;

return width >= 600
    ? const SidebarLayout()
    : const StackLayout();
Drawn to iPhone Duo’s point dimensions.How to fix it — Portrait lock, ignored

Symmetric EdgeInsets

Flutter has always exposed left and right padding separately, so this is a matter of discipline rather than a missing API. Any EdgeInsets.symmetric built from one side, or any padding.horizontal / 2, assumes the two edges match. On iPhone Duo they do not.

Left inset used for both edges versus Each edge its own insetTwo iPhone Duo inner displays with a narrow safe-area inset on the left and a wide one on the right, shown hatched. Left: the left inset applied to both edges lets content run into the wide right inset. Right: each edge padded by its own inset keeps content inside the safe area.

Left: Left inset used for both edges

final p = MediaQuery.paddingOf(context);

Padding(
  padding: EdgeInsets.symmetric(
    horizontal: p.left,
  ),
  child: content,
)

Right: Each edge its own inset

final p = MediaQuery.paddingOf(context);

Padding(
  padding: EdgeInsets.only(
    left: p.left,
    right: p.right,
  ),
  child: content,
)
Inset widths are exaggerated for legibility. Apple states the insets differ; it has not published the values.How to fix it — Left inset used for both edges

Split View looks frozen

Every app takes part in Split View on iPhone Duo. An app there can be visible while inactive, so video, animation controllers and timers paused on that state stop in front of the user.

Paused while still on screen versus Paused only when hiddenTwo iPhone Duo inner displays in Split View, with another app on the left and a video app on the right. Left: the video has stopped because the app paused itself on becoming inactive, though it is still on screen. Right: the video keeps playing until the app is actually in the background.

Left: Paused while still on screen

@override
void didChangeAppLifecycleState(
    AppLifecycleState state) {
  if (state ==
      AppLifecycleState.inactive) {
    controller.pause();
  }
}

Right: Paused only when hidden

@override
void didChangeAppLifecycleState(
    AppLifecycleState state) {
  if (state ==
      AppLifecycleState.paused) {
    controller.pause();
  }
}
Drawn to iPhone Duo’s point dimensions.How to fix it — Paused while still on screen

16:9 video on a 1.42 display

A centred 16:9 AspectRatio fitted to the 890-point width leaves 125 points of black bars. The natural Flutter answer is a Column: the player at 16:9, with related content in an Expanded beneath it.

16:9 fitted to 890 pt: 125 pt of bars versus Player plus content beneathLeft: a 16:9 video fitted to an iPhone Duo inner display held landscape, 890 by 626 points, leaves 125 points of black bars. Right: the display held portrait with the player at the top and related content filling the space beneath it.125 pt of bars626 pt

Left: 16:9 fitted to 890 pt: 125 pt of bars

Center(
  child: AspectRatio(
    aspectRatio: 16 / 9,
    child: VideoPlayer(controller),
  ),
)

Right: Player plus content beneath

Column(children: [
  AspectRatio(
    aspectRatio: 16 / 9,
    child: VideoPlayer(controller),
  ),
  const Expanded(child: RelatedList()),
])
Drawn to iPhone Duo’s point dimensions.How to fix it — 16:9 fitted to 890 pt: 125 pt of bars

The open question: is displayFeatures populated on iOS?

Whether the iOS embedder fills displayFeatures from Apple’s reservedRegions API is not something Apple’s material settles, and Flutter has announced nothing. Assume the list is empty on iPhone Duo until a real build shows otherwise.

If displayFeatures is empty: content on the fold versus If populated: split around itTwo iPhone Duo inner displays held open like a book, 890 by 626 points, with the fold shown as a band running top to bottom through the middle. Left: a button centred on the screen sits directly on the fold. Right: content on the left of the fold and controls on the right, with nothing across it.

Left: If displayFeatures is empty: content on the fold

final hinges = MediaQuery.of(context)
    .displayFeatures
    .where((f) => f.type ==
        DisplayFeatureType.hinge);
// [] → nothing to lay out around

Right: If populated: split around it

final mq = MediaQuery.of(context);
debugPrint('${mq.displayFeatures}');
// Populated → TwoPane patterns apply.
// Empty → a platform channel.
Drawn to iPhone Duo’s point dimensions.How to fix it — If displayFeatures is empty: content on the fold

If it is populated, the Android foldable patterns carry straight across, and Flutter is better placed than any other cross-platform stack. If it is empty, you can write a platform channel that wraps reservedRegions(kind:) and passes the frames into Dart, or accept a layout that is correct but unaware of the fold. ArrangementView and the vertical bar APIs have no Dart equivalent either way.

Find them in your codebase

Each of these searches maps to one of the blockers above.

grep -rn "MediaQuery.of(context).size" lib/
grep -rn "setPreferredOrientations" lib/
grep -rn "EdgeInsets.symmetric(horizontal" lib/
grep -rn "AppLifecycleState.inactive" lib/

Fix those four first. None of them depends on the answer to the displayFeatures question, and together they account for most of the visible improvement.

For the long-form version, including the breakpoint and lifecycle details, read Flutter on iPhone Duo: the right abstraction, possibly unwired.

Free evaluation

Want this done for your Flutter app?

We adapt existing iOS apps to iPhone Duo. Send your email and an engineer replies with what is likely to break in your Flutter app, what to fix first, and a realistic estimate of the work.

Takes about 20 seconds

See what an audit costs

Checklist items that apply to Flutter

Open the full checklist

Weighing Flutter against the others? All six frameworks scored side by side — or see our iPhone Duo development services if you would rather hand the work over.

Users already reporting that your app looks wrong? What each symptom means, in plain language you can send them.