duo_kit 0.1.1 copy "duo_kit: ^0.1.1" to clipboard
duo_kit: ^0.1.1 copied to clipboard

iPhone Duo and foldable layouts for Flutter: navigation in the strip, a split view across the fold and dialogs in one half, around your own widgets.

Banner

duo_kit #

duo_kit pub.dev badge

Flutter layouts for iPhone Duo and other foldables.

On iPhone Duo, FoldScaffold moves your navigation into the strip at the side of the window, and FoldSplitView puts a list and its details on either side of the fold when the device opens like a book. duo_kit only places widgets: the navigation and the panes are your own, in whatever design your app has.

The example app on iPhone Duo: as the device opens like a book, the list and its details move to either side of the fold, and back when it lies flat

Features #

  • FoldGeometry – the strip, the fold and the occlusions of the window, computed from MediaQuery alone.
  • FoldScaffold – puts your navigation along the bottom edge or, where the window has a strip, in the strip, and pads the body to match.
  • FoldSplitView – lays two panes out on either side of a fold, with your own layout while there is none, and keeps their state when the fold comes and goes.
  • FoldAnchor – the anchorPoint that opens a dialog or a sheet in the right half of a window split by a fold.
  • Development tools – simulate a fold on any device and see the geometry over your app.
  • Test poses – the window metrics of every iPhone Duo pose, for widget and golden tests.

duo_kit or adaptive_platform_ui #

adaptive_platform_ui gives your app the platform's own widgets: native UIKit toolbars and tab bars with Liquid Glass on iOS 26, Cupertino on older iOS and Material on Android. On iPhone Duo it lays out its own toolbar in the strip. Pick it if your app should look like the platform.

duo_kit draws nothing. It lays out the widgets you already have, in your own design, and handles the rest of the foldable: two panes split at the fold and dialogs that open in one half. It suits apps with a look of their own, or Material apps on iOS.

Installation #

flutter pub add duo_kit

Where the geometry comes from #

duo_kit reads only MediaQuery.viewPadding and MediaQuery.displayFeatures:

  • Android – Flutter fills the display features of a foldable by itself.

  • iOS – nothing fills them yet. Publish the fold and the camera with a plugin that reads the reserved regions of the window, such as foldable with DisplayFeatureBridgeMode.full:

    FoldableProvider(
      bridgeMode: DisplayFeatureBridgeMode.full,
      child: app,
    )
    

    Place it above your app, so that overlays outside the navigator see the fold too. While the device is closed, foldable publishes nothing; to keep the strip of the cover display clear of the status cluster, add its occlusions yourself, as ClosedFoldOcclusions in the example does.

    duo_kit_ios, in development in the same repository, will publish all of them natively, the closed device included.

    With the fold published, Flutter keeps dialogs, sheets and pickers on one side of it while the device is half open, as it does on Android.

iPhone Duo setup #

  • Build with the iOS 27.1 SDK (Xcode 27.1). With an older SDK, iOS runs the app in compatibility mode, with no strip and no reserved regions.
  • Adopt the UIScene life cycle. With the iOS 27 SDK an app without it crashes at launch. Projects created with Flutter 3.41 already have it; in an older one, make your AppDelegate a FlutterImplicitEngineDelegate, register plugins in didInitializeImplicitFlutterEngine, and add a UIApplicationSceneManifest with FlutterSceneDelegate to Info.plist.
  • Target iOS 15.0 or later, in the Runner target and in your pods. Xcode 27 rejects lower deployment targets, and the Flutter 3.41 template still sets 13.0.
  • Do not lock the orientation on a device with a hinge. iOS then scales the app instead of laying it out, and reports no fold. On the simulator this sticks to the bundle ID until the simulator reboots.

Usage #

FoldScaffold(
  body: SafeArea(bottom: false, child: page),
  navigationBuilder: (context, placement) => MyNavigationBar(
    // Lay the items out along placement.axis: a row at the bottom, a column
    // in the strip.
    axis: placement.axis,
  ),
)

The navigation gets the MediaQuery padding of where it sits – the bottom inset, or in the strip the room taken by the camera and the status cluster – so a SafeArea inside it keeps it clear. The body stays the same widget in every pose and keeps its state.

The navigation in the strip of the cover display, below the status cluster, and along the bottom edge of the inner display in portrait

In a strip, placement is left or right, the side the strip runs along. On iPhone Duo it is left only on the cover display in landscape, with the camera on the left. Put whatever faces the content, such as a divider or a selection indicator, on the edge towards the body.

Two panes #

FoldSplitView(
  first: list,
  second: details,
  unfoldedBuilder: (context, first, second) => Row(
    children: [
      SizedBox(width: 320, child: first),
      Expanded(child: second),
    ],
  ),
)

While a fold crosses it, FoldSplitView puts first on the leading side of a vertical fold or above a horizontal one, and second on the other side. Each pane keeps only the padding and the view insets, such as the keyboard, of the edges it touches. Without a fold it uses your layout; put each pane in it once. Decide when two panes fit with your own breakpoint.

A pane your layout leaves out loses its state. To keep it, hide it instead:

unfoldedBuilder: (context, first, second) => Stack(
  fit: StackFit.expand,
  children: [
    first,
    Offstage(child: TickerMode(enabled: false, child: second)),
  ],
),

Dialogs and sheets #

showDialog(
  context: context,
  anchorPoint: FoldAnchor.content.resolvePoint(context),
  builder: (context) => const MyDialog(),
);

FoldAnchor.content opens above a horizontal fold, FoldAnchor.controls below it; across a vertical fold both open on the trailing side. Overlays that are not routes can do the same with DisplayFeatureSubScreen.

iPhone Duo half open like a laptop: a reminder opens above the fold as content, and stream controls below it as controls

Geometry #

final geometry = FoldGeometry.of(context);

geometry.strip; // The strip at the side, with the clearances of its occlusions.
geometry.division; // The fold that splits the window, if any.
geometry.occlusions; // Cameras and the status cluster.
geometry.availableWidth; // The width without the side insets.

The geometry of four iPhone Duo poses, painted over the example app

The cover display and the inner display lying flat, then half open like a book and like a laptop, as FoldGeometryOverlay paints them: the strip in blue, the camera and the status cluster in orange, and the fold in red.

Development tools #

import 'package:duo_kit/debug.dart';

kDebugMode ? FoldDebugTools(child: app) : app

A button at the left edge switches between poses – a strip, a fold like a book, a fold like a laptop – on any device, and holding it shows or hides the geometry over your app. Place it below whatever publishes the real fold.

Tests #

import 'package:duo_kit/testing.dart';

MediaQuery(data: FoldTestPose.duoHalfOpenedLandscape.data, child: screen)

Limitations #

  • A strip is looked for on iOS only, since an Android phone in landscape can report the same insets for its camera cutout. Pass detectStrip to change that.
  • Apple's guidelines for iPhone Duo put the strip on the left for the app on the left of Split View, but iOS 27.1 reports no strip to a Flutter app there, so its navigation stays at the bottom.
  • FoldSplitView measures where it sits after it builds, resizes or scrolls, and catches up one frame later.
  • With foldable, the fold reaches the display features about a second after the device comes to rest half open, since the reserved regions follow the hinge with a delay.
  • Tested on the iPhone Duo simulator. Android foldables follow from the same display features, but have not been tested on a device.

🛠️ Maintained by LeanCode #

LeanCode Logo

This package is built with 💙 by LeanCode. We are top-tier experts focused on Flutter Enterprise solutions.

Why LeanCode? #

  • Creators of Patrol – the next-gen testing framework for Flutter.
  • Full-Cycle Product Development – We take your product from scratch to long-term maintenance.

Need help with your Flutter project?

👉 Hire our team   •   Check our other packages

0
likes
160
points
66
downloads
screenshot

Documentation

API reference

Publisher

verified publisherleancode.co

Weekly Downloads

iPhone Duo and foldable layouts for Flutter: navigation in the strip, a split view across the fold and dialogs in one half, around your own widgets.

Homepage
Repository (GitHub)
View/report issues

Topics

#iphone-duo #foldable #dual-screen #layout #adaptive

License

Apache-2.0 (license)

Dependencies

flutter

More

Packages that depend on duo_kit