Skip to content

feat: Add iOS 27 support - #7239

Open
cadivus wants to merge 12 commits into
mainfrom
ios27-support-no-xcode-update
Open

feat: Add iOS 27 support#7239
cadivus wants to merge 12 commits into
mainfrom
ios27-support-no-xcode-update

Conversation

@cadivus

@cadivus cadivus commented Aug 6, 2026

Copy link
Copy Markdown
Member

Same as #7234 but without upgrading the workflows to a beta Xcode version.

cadivus added 12 commits August 4, 2026 15:59
Replace raw NSError in FlutterError.details with nil in
didFailToRegisterForRemoteNotificationsWithError. NSError is not
supported by FlutterStandardCodec and causes
NSInternalInconsistencyException on encode.
Add FlutterSceneLifeCycleDelegate conformance and register via
addSceneDelegate. Implement scene:willConnectTo:options: for launch
notification extraction from connectionOptions.notificationResponse,
sceneDidBecomeActive for token registration, and sceneDidEnterBackground
for launch notification cleanup. Existing AppDelegate methods retained
for backward compatibility.
The scene callbacks added for UIScene support run once per connected scene,
while the state they drive is per application. Guard them accordingly:

- scene:willConnectToSession:options: returned true, which tells
  FlutterPluginSceneLifeCycleDelegate the connection was handled. The
  forwarding stops at the first plugin that returns true and nils out the
  connectionOptions, so returning true here hid the event, and the launch
  notification, from every plugin registered after this one. The plugin only
  reads the options, so it now returns false.

- sceneDidBecomeActive registered for remote notifications on every scene
  activation. In a multi scene App that repeats the APNs round trip per scene
  and emits a duplicate token, or duplicate DeviceTokenError, on the Dart
  event stream. It now registers only when no other scene is already active.

- sceneDidEnterBackground cleared the shared launchNotification whenever any
  scene was backgrounded, which could discard a tap another, still
  foregrounded, scene had not handed to Flutter yet. It now waits for the last
  foreground scene to leave.

Hosts that have not adopted UIScene are unaffected: the AppDelegate methods
in +AppDelegate.swift are unchanged and remain the active path there.
…ugin

The previous tests lived in the amplify_push_notifications example and
asserted on values they rebuilt inline, so they passed with the fixes
reverted: the details map test rebuilt the dictionary literal instead of
reading it back from the plugin, and the activation state test reduced to
XCTAssertNotEqual(.unattached, .background), a statement about the UIKit enum.

Replace them with tests in the existing unit_tests target, which can
@testable import the plugin and already has the registrar, flutter api and
UIApplication swizzling mocks:

- didFailToRegisterForRemoteNotifications forwards a {domain, code} map, and
  no NSError, so FlutterStandardCodec can encode it
- register(with:) installs both the application and the scene delegate
- scene:willConnectToSession:options: returns false, leaving the event for
  the plugins after this one, and keeps a launch notification recorded via
  the AppDelegate path when the options were already consumed
- sceneDidBecomeActive registers for remote notifications
- sceneDidEnterBackground nullifies the launch notification

MockFlutterPluginRegistrar gains the addSceneDelegate implementation that
FlutterPluginRegistrar now requires.
connectedScenes is an unordered Set, so connectedScenes.first can return a
background scene, or a scene without a screen, and the ?? fallbacks then
reported a 0x0 screen: amplify_auth_cognito sent that to Cognito as ASF
context data, and DeviceInfo.screenBounds returned .zero.

Look for a foregroundActive scene first, then foregroundInactive, and only
then fall back to any window scene. Cognito now leaves screenWidthPixels and
screenHeightPixels unset when no scene has a screen, rather than sending 0,
as both fields are optional in the pigeon message.

UIScreen.main is deprecated as of iOS 26. connectedScenes is available from
iOS 13, which both packages already require.
The unit_tests target did not compile against Flutter 3.44, so none of these
tests had been running:

- FlutterPluginRegistrar gained the viewController property and
  valuePublished(byPlugin:), which MockFlutterPluginRegistrar was missing, so
  the mock no longer conformed
- setBadgeCountWithBadgeCount takes an NSInteger, so the `as NSNumber` cast on
  the argument no longer type checked
- the pigeon generated types live in the amplify_push_notifications_objc
  module, which @testable import amplify_push_notifications does not
  re-export, leaving PushNotificationsFlutterApi unresolved

With these fixed the whole target builds and all 21 tests pass, including the
scene life cycle coverage added in the previous commit.
The iOS 27 SDK only supports deployment targets from 15.0, so building any
example on it fails outright rather than warning:

  Target Integrity (Xcode): The iOS Simulator deployment target
  'IPHONEOS_DEPLOYMENT_TARGET' is set to 13.0, but the range of supported
  deployment target versions is 15.0 to 27.0.x.
  Could not build the application for the simulator.

Xcode 26.4 still accepts 12.0 and above, which is why this only shows up on
the iOS 27 CI runners.

Raise every example to 15.0: the platform line in 12 Podfiles, the
IPHONEOS_DEPLOYMENT_TARGET in the Runner projects, and the amplify_api
post_install hook that pinned the Pods project to 13.0. Also drop the
MinimumOSVersion key from AppFrameworkInfo.plist, which the current Flutter
template no longer generates and which still claimed 11.0 or 12.0.

This only covers the examples. The published packages still declare iOS 13.0
in their podspecs and Package.swift files, as raising that is a breaking
change for consumers and belongs in its own change.
Raising the Podfile platform was not enough. podhelper.rb generates a stand-in
Flutter podspec with a hardcoded 13.0 deployment target, and its own
flutter_additional_ios_build_settings only clears IPHONEOS_DEPLOYMENT_TARGET
for pods below 13.0, so the Flutter pod kept 13.0 in Pods.xcodeproj and the
build still failed on the iOS 27 SDK:

  Target Integrity (Xcode): The iOS Simulator deployment target
  'IPHONEOS_DEPLOYMENT_TARGET' is set to 13.0, but the range of supported
  deployment target versions is 15.0 to 27.0.x.
  .../example/ios/Pods/Pods.xcodeproj

Override the setting for every Pods target in post_install, matching what the
amplify_api example already did for a different reason. Also raise the iOS
platform in the plugin podspecs and Package.swift files, which is what pod
install and Swift Package Manager resolve against.

The macOS deployment targets are untouched.
Flutter 3.41 makes UIScene the default, and without it the tooling cannot
attach to the app on the iOS 27 simulator, so the integration tests never
start:

  Error waiting for a debug connection: The log reader failed unexpectedly
  Failed to load ...main_test.dart: Unable to start the app on the device.

Ran the migration the Flutter CLI performs for app developers, by building
each example once and letting it report "Finished migration to UIScene
lifecycle". It adds the UIApplicationSceneManifest to Info.plist and moves
plugin registration from didFinishLaunchingWithOptions to
didInitializeImplicitFlutterEngine.

The CLI skips the migration when it does not recognise the AppDelegate, and it
compares against its templates verbatim, so our copyright header alone was
enough to opt every example out of it. The header was removed for the migration
and restored afterwards.

No SceneDelegate subclass is added: none of these examples use the deprecated
UI life cycle events, so they use Flutter's own FlutterSceneDelegate, which is
what the CLI configures.
@codecov-commenter

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 40.97%. Comparing base (7c5ffa8) to head (9605c58).
⚠️ Report is 1 commits behind head on main.

Additional details and impacted files
@@           Coverage Diff           @@
##             main    #7239   +/-   ##
=======================================
  Coverage   40.97%   40.97%           
=======================================
  Files         121      121           
  Lines        8273     8273           
  Branches     3598     3598           
=======================================
  Hits         3390     3390           
  Misses       4883     4883           
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@cadivus
cadivus requested a review from harsh62 August 6, 2026 12:18
@cadivus
cadivus marked this pull request as ready for review August 6, 2026 12:22
@cadivus
cadivus requested a review from a team as a code owner August 6, 2026 12:22
@cadivus
cadivus enabled auto-merge (squash) August 6, 2026 12:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants