[ECO-5694] Adopt UIScene life cycle for iOS push notifications - #603
[ECO-5694] Adopt UIScene life cycle for iOS push notifications#603ttypic wants to merge 7 commits into
Conversation
The UIScene plugin APIs this SDK is about to adopt — the `FlutterSceneLifeCycleDelegate` protocol and `FlutterPluginRegistrar.addSceneDelegate:` — first shipped in Flutter 3.38, and `UIScene` itself is iOS 13+. Raise both floors so the iOS migration in the following commits can compile, and pull the toolchain and CI up to Flutter 3.41.9 to match. Alongside the version bumps: - Drop the removed `package_api_docs` lint and the two now-redundant `!` assertions in `codec.dart` that the newer analyzer flags, so `flutter analyze` is clean again. - Silence `experimental_member_use` on the example app's deliberate use of the experimental `Push.reset`. - Gitignore `ios/Flutter/ephemeral/`, which Flutter 3.41 generates in both example projects. - Regenerating the CocoaPods projects also drops stale Firebase, Toast and nanopb framework entries from the example's `project.pbxproj`; those pods are no longer in its Podfile. Groundwork for #592 and #602. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`dart format --set-exit-if-changed .` is a CI gate, and the formatter's output changed with the SDK bump in the previous commit, so the whole tree has to be reformatted. Kept as its own commit because it touches 134 files and none of it is behavioural — review it separately from, or skip it alongside, the UIScene work that follows. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…hLaunching Apple requires `UNUserNotificationCenter.delegate` to be set before `application:didFinishLaunchingWithOptions:` returns, but for apps on the UIScene life cycle Flutter defers plugin registration until after it returns. The plugin installed the delegate during registration, which is too late — so expose a shared instance and an explicit API that apps call themselves, as the Flutter team asked in #602: AblyFlutter.sharedInstance().registerPushNotificationHandlers() `AblyFlutter` becomes a process-wide singleton to make that reachable before Flutter has registered anything. Its remaining state was already process-wide (`AblyInstanceStore` and `PushActivationEventHandlers` are both singletons), and registration now configures the shared instance rather than allocating a new one. Plugin registration still installs the delegate itself, so apps on the `UIApplicationDelegate` life cycle need no source changes. Installing is now idempotent — previously each call wrapped whatever delegate was present, so being invoked twice would have delivered every notification event to Dart once per wrapper. Because the delegate can now be installed before any Flutter engine exists, `PushNotificationEventHandlers` takes its method channel separately from construction and states what each handler does while there is no channel. The one case that must not be lost is a tap that launched the app: its payload goes to the property Dart already polls on startup. Two bugs on that same path, which the UIScene work depends on: - `pushNotificationTapLaunchedAppFromTerminated` returned the raw APNs payload, which the codec cannot encode as the `RemoteMessage` Dart expects, and never delivered a result at all when the app was not launched by a tap — leaving Dart awaiting forever. - `application:didReceiveRemoteNotification:fetchCompletionHandler:` silently dropped the notification when `AblyFlutterHandlePushNotifications` is `NO`, never calling the completion handler. It now declines the event so another application delegate can handle it. Also drops `UNUserNotificationCenterDelegate` from `AblyFlutter`'s declared conformances: it implements none of those methods (`PushNotificationEventHandlers` does), so the conformance only invited callers to install the wrong object. Resolves #602. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Apple will require UIKit apps built against the SDK after iOS 26 to use the UIScene life cycle, and Flutter is following, so the plugin has to handle `UISceneDelegate` events as well as `UIApplicationDelegate` ones. `AblyFlutter` now conforms to `FlutterSceneLifeCycleDelegate` and registers via `addSceneDelegate:`, implementing `scene:willConnectToSession:options:` — the scene equivalent of `application:didFinishLaunchingWithOptions:`. It stays registered as an application delegate too, so apps on either life cycle work. The reason this event is needed rather than optional: Flutter does forward `application:didFinishLaunchingWithOptions:` to plugins in a scene app, but with nil launch options. So the check for a notification tap having launched the app, which read `UIApplicationLaunchOptionsRemoteNotificationKey`, would silently stop finding anything. The payload now comes from the scene's `connectionOptions.notificationResponse` instead, and whichever of the two callbacks runs first wins. Nothing else in the plugin needed migrating: the only `UIApplication` use is `registerForRemoteNotifications`, there are no observers on `UIApplication` notifications, and none of the deprecated `keyWindow` / `UIScreen.mainScreen` / `UIApplication.windows` accessors are used. Foreground and background state is read from `UIApplication.applicationState` on demand, which still works under UIScene. Resolves #592. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ne life cycle Neither app opted into UIScene, so nothing exercised the scene code paths the previous two commits added. Both now declare a `UIApplicationSceneManifest` and call `registerPushNotificationHandlers` from their `AppDelegate`, which is also the reference the docs point at. The manifest names Flutter's own `FlutterSceneDelegate` as the scene delegate class rather than introducing an app-level subclass, since neither app needs to customise it — its header documents this as the intended use. `UIMainStoryboardFile` goes away because `UISceneStoryboardFile` supersedes it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`pod lib lint` resolves `s.dependency 'Flutter'` from CocoaPods trunk, where the `Flutter` pod is pinned at 3.13.0. That predates the UIScene plugin APIs, so the iOS unit test job started failing with "cannot find protocol declaration for 'FlutterSceneLifeCycleDelegate'". The `Flutter.podspec` that Flutter tooling generates inside an app project is no help either — it is a placeholder with no headers, because real builds link the framework through xcconfig rather than CocoaPods. So supply a development-only podspec that vendors the engine artifacts of whichever Flutter SDK is on PATH, and pass it to lint with `--include-podspecs`. It symlinks the framework in beside itself because CocoaPods rejects absolute file patterns and silently ignores relative ones that escape the pod root. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Adds a UIScene section to PushNotifications.md covering the `registerPushNotificationHandlers` call scene-based apps must make, in Swift and Objective-C, and why Apple's ordering requirement makes it necessary. Adds a migration entry to UPDATING.md for that call and for the Flutter 3.38 / iOS 13 minimums. Also documents the `AblyFlutterHandlePushNotifications` Info.plist opt-out, which has been undocumented since it was added, and fixes references to an example `AppDelegate.m` that has been Swift for some time. The claim that foreground presentation is decided in the app's AppDelegate was wrong too — the plugin owns `willPresentNotification` and asks Dart. CHANGELOG entries are left to the release process, which generates them from merged PRs. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Important Review skippedToo many files! This PR contains 169 files, which is 69 over the limit of 100. To get a review, reduce the PR to 100 files or fewer by splitting it into smaller PRs or changing its base branch. Upgrade to a paid plan to raise the limit. This review couldn't start because sufficient usage credits or metered capacity aren't available. Add credits or update usage-based reviews in the billing tab, then retry. ⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Plus Run ID: ⛔ Files ignored due to path filters (5)
📒 Files selected for processing (169)
You can disable this status message by setting the Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
introduces support for the UIScene architecture, which is required by iOS SDK builds targeting iOS 26 and later:
Plugin updates for UIScene support:
FlutterSceneLifeCycleDelegateto handleUISceneDelegateevents alongsideUIApplicationDelegateones.UIApplicationLaunchOptionsRemoteNotificationKeyandUIScene’s connection options.Example and integration-test updates:
UIApplicationSceneManifestand using the recommendedFlutterSceneDelegate.Push notification integration adjustments:
AblyFlutter.sharedInstance().registerPushNotificationHandlers()before Flutter plugin registration.pushNotificationTapLaunchedAppFromTerminated.Compatibility bumps:
Additional improvements:
PushNotifications.mdandUPDATING.md.