dual_screen_hinge 0.2.1
dual_screen_hinge: ^0.2.1 copied to clipboard
iPhone Duo-first foldable support for Flutter: hinge angles, postures, reserved regions, screen roles, and Android display modes.

dual_screen_hinge #
iPhone Duo-first foldable support for Flutter: continuous hinge angles, posture, reserved regions, screen role, and Android display modes.
Use it alongside MediaQuery #
On Android, prefer MediaQuery.displayFeatures for adaptive layout and the
basic opened/half-opened state Flutter already exposes. Use this plugin when
you need continuous angles for animation, richer fold/hinge metadata,
supported postures, rear/dual-screen display sessions, or iOS support.
Install #
dependencies:
dual_screen_hinge: ^0.2.1
Requires Flutter 3.38+, Dart 3.10+, Android API 21+, or iOS 13+. iPhone Duo
hinge telemetry requires a UIKit SDK that declares UIHingeInteraction.
Reserved-region geometry requires an SDK that declares reservedRegions.
CocoaPods and Swift Package Manager detect these APIs automatically; Xcode 27.0
SDKs without them keep the safe, unsupported fallback. No Podfile workaround
is needed. Runtime support still depends on the iOS version and hardware.
Read state and animate #
final subscription = DualScreenHinge.instance.events.listen((state) {
print('${state.posture} ${state.hingeAngle}°');
});
StreamBuilder<double>(
stream: DualScreenHinge.instance.hingeAngleEvents,
builder: (context, snapshot) {
final angle = snapshot.data ?? 180;
return Transform.rotate(
angle: (180 - angle) * math.pi / 180,
child: const MyFoldEffect(),
);
},
);
The native event stream is shared by all Dart listeners, sends a snapshot on listen, deduplicates state, and coalesces sensor updates to approximately one per display frame.
Read regions for custom layout #
final state = await DualScreenHinge.instance.currentState();
final activeRegionBounds = state.reservedRegions
.where((region) => region.isActive)
.map((region) => region.bounds)
.toList(growable: false);
On iOS 27.1, reservedRegions contains the folding region (division) and
camera regions (occlusion) in Flutter logical coordinates. Inactive regions
are included so a grid can, for example, prefer an even number of columns
before the fold becomes active. Active division regions are also exposed in
displayFeatures for cross-platform layout code.
Use MediaQuery.sizeOf(context), MediaQuery.paddingOf(context), and flexible
constraints for the overall layout. Use reserved regions only to displace
important custom-positioned elements using activeRegionBounds; don't drive
layout from the hinge angle or fixed iPhone Duo dimensions. See Apple's
iPhone Duo HIG.
Display modes (Android) #
final capabilities = await DualScreenHinge.instance.capabilities();
if (capabilities.rearDisplay) {
await DualScreenHinge.instance.startRearDisplay();
}
if (capabilities.dualScreenPresentation) {
await DualScreenHinge.instance.startDualScreen(
entrypoint: 'dualScreenSecondaryMain',
arguments: ['initial payload'],
);
}
@pragma('vm:entry-point')
void dualScreenSecondaryMain(List<String> arguments) {
runApp(SecondaryDisplayApp(arguments: arguments));
}
Only one display session can be pending or active. V1 deliberately supports
startup arguments, not a cross-engine message bus. On iOS, display-mode methods
return DualScreenException(code: 'unsupported') because there is no equivalent
app-controlled public session API.
Platform behavior #
| Capability | Android | iOS |
|---|---|---|
| Continuous angle | API 30+ TYPE_HINGE_ANGLE when present |
iOS 27 UIHingeInteraction on supported hardware |
| Fold geometry/posture | AndroidX WindowManager 1.5.1 | iOS 27 hinge status; iOS 27.1 reserved regions |
| Camera/fold avoidance | Flutter MediaQuery.displayFeatures |
Division and occlusion reserved regions on iOS 27.1 |
| Inner/outer identity | Only while a Window Area session establishes it | Region and scene evidence; unknown when multitasking is ambiguous |
| Rear-display transfer | Experimental Window Area API | Unsupported |
| Dual-screen presentation | Experimental Window Area API + secondary Flutter engine | Unsupported |
No device allowlist, OEM reflection, or private API is used. Unsupported data is nullable or reported through runtime capabilities.
Flutter-rendered toolbars don't automatically become UIKit's iPhone Duo vertical bars. Keep controls inside each independent safe-area edge, preserve their relative order across sizes, and let decorative backgrounds extend edge-to-edge separately from interactive content.
Documentation #
See the documentation site,
the complete wire contract, device matrix,
the deployment guide, and testing guide.
The example/ app includes a
live fold animation and compares plugin output with MediaQuery.displayFeatures.
License #
MIT. See LICENSE.
