wip(ios): adopt the UIScene lifecycle so the app can launch on iOS 26+ - #11570
wip(ios): adopt the UIScene lifecycle so the app can launch on iOS 26+#11570formed2forge wants to merge 6 commits into
Conversation
Info.plist and Info-Dev.plist both set UIMainStoryboardFile = Main, but there is no Main.storyboard in ios/Runner/ — only devLaunchScreen.storyboard and prodLaunchScreen.storyboard (the built bundle contains only those two .storyboardc). The app-delegate lifecycle tolerated the dangling reference; UISceneStoryboardFile could not be set to the same nonexistent name without aborting at launch (see the manifest added in 05b3312), which is what surfaced this as worth fixing rather than leaving as a latent oddity. Removed from both plists with PlistBuddy; plutil -lint passes on each. Answers open question 3 on BasedHardware#11570. Failure-Class: none Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Git-on-my-level
left a comment
There was a problem hiding this comment.
Thanks @formed2forge — this is a well-documented attack on a real launch-blocker (#11568), and the migration direction matches both Apple's iOS 26 SDK requirement and Flutter's official UIScene migration template. Static review notes below; nothing here is a merge blocker yet since this is draft and device-gated.
AppDelegate.swift
- Verified all ten base-version force-unwraps rooted in
window?.rootViewController(e.g. the oldcontroller!.binaryMessengerin the WCSession block and the unconditionallet controller = window?.rootViewController ... !chain) are gone; every channel now sources its messenger fromengineBridge.applicationRegistrar.messenger(), andOmiPhoneCallsPlugin.registernow usesregistry.registrar(forPlugin:)instead ofself.registrar(forPlugin:). That removes the launch-time crash class under scenes, not just the symptoms. - Good call anchoring foreground reconnect to
UIApplication.willEnterForegroundNotificationinstead of a scene delegate, with the 0/4 vs 4/4 measurements noted — that also survives any future multi-scene flip. - One carried-over gap: in
didFinishLaunchingWithOptionsthe deep-link branch still returnstruebeforeSwiftFlutterForegroundTaskPlugin.setPluginRegistrantCallbackand theUNUserNotificationCenterdelegate assignment. The PR description calls out fixing the old early-return-skips-registration bug, but a cold launch that carries a link (e.g. a Ray-Ban Meta AppLink) still skips the foreground-task callback whose own comment says "Without this code the task will not work". Suggest moving theAppLinkscheck after those two setup steps (registration no longer shares that path anyway) or handling the link post-setup. - Minor: the BLE / Ray-Ban / phone-mic blocks now use
do { ... }with nocatch— that's just a scoping block in Swift, but it reads like error handling. Consider dropping the wrapper or keeping a comment. - For the hardware checklist: under the scene lifecycle iOS normally routes URL opens to
scene(_:openURLContexts:)rather thanapplication(_:open:options:), which is where the Ray-Ban registration callback is handled. Worth explicitly testing warm and cold Ray-Ban callback flows on device before un-drafting.
Info.plist / Info-Dev.plist
- Manifest structure matches Flutter's documented template (
UIWindowSceneSessionRoleApplication, configuration nameflutter,FlutterSceneDelegate,UIApplicationSupportsMultipleScenes=false), and removingUIMainStoryboardFileis consistent with it —project.pbxprojreferences no Main.storyboard, so that key was stale anyway. Carrying the identical change intoInfo-Dev.plist(what the dev xcconfigs actually build against) avoids the stale-tracked-copy crash; good catch.
CI note: mobile-app-checks is Android/Dart-only, so nothing in CI exercises this change — device validation stays the real gate, which I know is already on your radar per "What's left". Labeling needs-maintainer-review for that reason (a maintainer with iOS hardware needs to sign off on launch/deep-link/foreground-task behavior before this leaves draft), not as a knock on the work.
by AI on behalf of David — if you need David’s attention urgently, please @Git-on-my-level and escalate with need human response.
|
Two companion fixes found while verifying this migration on-device, both needed to actually see the app work end to end:
Combining all three, the app reaches the sign-in screen on iPhone 17 Pro / iOS 27.0 — confirmed on real hardware, not just a live process. |
Git-on-my-level
left a comment
There was a problem hiding this comment.
Thanks @formed2forge — reviewed the new head (239c3570) on top of the earlier pass. Three updates, each verified against the repo:
Restored UISceneStoryboardFile — verified, and a correction to my own earlier note. app/ios/Runner/Base.lproj/Main.storyboard does exist (a stock FlutterViewController initial scene), and it is wired into Runner.xcodeproj's resource build phases for both the prod and dev targets — so my previous statement that "project.pbxproj references no Main.storyboard" was wrong, and restoring the key in both Info.plist and Info-Dev.plist is the correct manifest. The post-mortem on why "process alive, no crash" was a false signal for debug-mode engine creation is worth keeping in the description for the next person who tries to validate this way.
Companion PRs landed on main, but this branch doesn't contain them yet. #11594 and #11595 are both merged, and your comment correctly identifies the flutter_contacts 2.x migration as a merge prerequisite for this PR. As of this head, app/pubspec.yaml on the branch still pins flutter_contacts: ^1.1.9+2 (main has ^2.3.1) and the branch is several commits behind main — so a fresh checkout of this branch alone still hits the 1.x registration crash you diagnosed in #11595. Merging main into this branch before the final device-validation pass ("What's left") will make the branch self-contained and let CI reflect the prerequisite.
Still open from the last pass (unchanged, non-blocking while draft):
AppDelegate.swift→didFinishLaunchingWithOptions: theAppLinks.shared.getLinkbranch still returnstruebeforeSwiftFlutterForegroundTaskPlugin.setPluginRegistrantCallbackand theUNUserNotificationCenterdelegate assignment, so a cold launch carrying a deep link still skips the foreground-task callback whose own comment says "Without this code the task will not work."- The three
do { ... }scoping blocks (BLE / Ray-Ban / phone-mic registration insidedidInitializeImplicitFlutterEngine) still read like error handling without acatch; a plain block or a comment would be clearer. - Device checklist still needs the scene-lifecycle URL-open routing check (
scene(_:openURLContexts:)vsapplication(_:open:options:)) for the Ray-Ban Meta callback flow, warm and cold.
The core migration remains sound on static review: all thirteen channel/Pigeon/plugin registration sites (watch recorder, BLE, Ray-Ban, phone-mic, notifyOnKill, reminders, health, speech, environment, audioSession, WiFi, battery widget, phone-calls) now source their messenger from engineBridge.applicationRegistrar.messenger(), OmiPhoneCallsPlugin registers via registry.registrar(forPlugin:), and the foreground BLE reconnect is anchored to UIApplication.willEnterForegroundNotification — which also survives a future multi-scene flip.
Keeping needs-maintainer-review: launch, deep-link, and foreground-task behavior under the scene lifecycle can only be confirmed on iOS hardware, which no CI job here covers.
Automated review by glm-5.3 acting for the maintainers. Human sign-off needed from a maintainer with iOS 26+ hardware to validate launch, deep-link, and foreground-task flows on device before this leaves draft.
by AI on behalf of David — if you need David’s attention urgently, please @Git-on-my-level and escalate with need human response.
239c357 to
862fe6e
Compare
Info.plist and Info-Dev.plist both set UIMainStoryboardFile = Main, but there is no Main.storyboard in ios/Runner/ — only devLaunchScreen.storyboard and prodLaunchScreen.storyboard (the built bundle contains only those two .storyboardc). The app-delegate lifecycle tolerated the dangling reference; UISceneStoryboardFile could not be set to the same nonexistent name without aborting at launch (see the manifest added in 05b3312), which is what surfaced this as worth fixing rather than leaving as a latent oddity. Removed from both plists with PlistBuddy; plutil -lint passes on each. Answers open question 3 on BasedHardware#11570. Failure-Class: none Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
Thanks @formed2forge — quick pass over the rebased head ( The rebase resolves the prerequisite flagged last round — verified. The branch now sits on current main and The PR's own changes are byte-identical to the last-reviewed state. I diffed all three files ( Still open (unchanged, non-blocking while draft):
Nothing new blocking on static review. The remaining gate before un-drafting is still on-hardware validation of launch, deep-link routing, swipe-kill, and foreground-task behavior under the scene lifecycle. by AI on behalf of David — the remaining gate is sign-off from a maintainer with iOS 26+ hardware; please @Git-on-my-level if David is needed. |
Info.plist and Info-Dev.plist both set UIMainStoryboardFile = Main, but there is no Main.storyboard in ios/Runner/ — only devLaunchScreen.storyboard and prodLaunchScreen.storyboard (the built bundle contains only those two .storyboardc). The app-delegate lifecycle tolerated the dangling reference; UISceneStoryboardFile could not be set to the same nonexistent name without aborting at launch (see the manifest added in 05b3312), which is what surfaced this as worth fixing rather than leaving as a latent oddity. Removed from both plists with PlistBuddy; plutil -lint passes on each. Answers open question 3 on BasedHardware#11570. Failure-Class: none Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
862fe6e to
c6b94d3
Compare
Git-on-my-level
left a comment
There was a problem hiding this comment.
Thanks @formed2forge — reviewed the rebased head (c6b94d3) against the previous pass (862fe6e6) and current main.
The rebase is clean — verified. The PR's own changes are unchanged in substance: same FlutterImplicitEngineDelegate conformance with plugin registration moved into didInitializeImplicitFlutterEngine(_:), same messenger sourcing across the channel setups, same willEnterForegroundNotification anchor for BLE reconnect, and both Info.plist and Info-Dev.plist carry the identical scene manifest with UISceneStoryboardFile restored. Everything else new in the head vs the last review (BGTaskScheduler cancels, the NSAllowsArbitraryLoads dev-plist comment, dictation punctuation hints, telemetry marks) came in from main via the rebase, not from this branch. Also verified UIMainStoryboardFile existed in exactly these two plists repo-wide, so dropping it is complete.
New from the rebase — the #11892 background-energy telemetry will go silent under this PR. The head now inherits applicationDidEnterBackground / applicationDidBecomeActive overrides from main (#11892, OmiBleManager.markBackgroundTelemetryStart/End). Those are app-delegate UI-state callbacks — the same class as applicationWillEnterForeground, which you measured at 0/4 under the scene lifecycle. UIKit stops calling them once this manifest ships, so the telemetry landed on Aug 20 would silently stop collecting the moment this merges. The fix is the same shape you already applied for reconnect: anchor to UIApplication.didEnterBackgroundNotification / didBecomeActiveNotification (your 4/4 result for the app-level willEnterForegroundNotification suggests the notification family keeps firing — worth confirming on device). Folding that in before the hardware pass means the validation covers it.
Sharpened from last round — the Ray-Ban URL callback looks statically dead. rayBanMetaHostApi?.handleUrl(url) lives in application(_:open:options:), another callback the scene delegate takes over; on scene-lifecycle apps URL opens route to scene(_:openURLContexts:) instead. Unless routing is added scene-side, the DAT-camera callback flow never fires. This was on the device checklist; statically it now reads as a definite gap rather than a to-verify.
Still open, unchanged from the prior review: the AppLinks.shared.getLink early return true in didFinishLaunchingWithOptions still skips SwiftFlutterForegroundTaskPlugin.setPluginRegistrantCallback and the UNUserNotificationCenter delegate assignment on a deep-link cold launch, and the three do { … } scoping blocks still read like error handling with no catch.
One note for maintainers: nothing in CI compiles app/ios/Runner/AppDelegate.swift (the Swift checks in this repo cover desktop only), so hardware runs remain the only compile + runtime validation for this migration.
by AI on behalf of David — the remaining gate is sign-off from a maintainer with iOS 26+ hardware, specifically to confirm the #11892 telemetry callbacks and Ray-Ban URL routing under the scene lifecycle before this leaves draft.
Partial, deliberately incomplete — see BasedHardware#11568. Opening as a draft because the remaining half is an AppDelegate migration that needs a decision on approach before it is written. Apps built against the iOS 26+ SDK must adopt the UIScene lifecycle. Without UIApplicationSceneManifest, UIKit traps during scene setup and the Dart VM never starts, so the app sits on the launch storyboard forever with no crash, no UI, and no Dart code executed at all. This commit adds only the manifest. Measured effect on iPhone 17 Pro / iOS 27.0 with Flutter 3.44.9: before: "The Dart VM Service was not discovered after 60 seconds", EXC_BREAKPOINT in UIKitCore via FrontBoardServices, signal 5 after: Dart VM Service discovered (http://127.0.0.1:50817/...), DartWorker and AudioSession threads live, then SIGABRT So the manifest clears the native launch trap but is not sufficient on its own. UISceneStoryboardFile is intentionally omitted: Flutter's template sets it to "Main" and this project has no Main.storyboard, only devLaunchScreen and prodLaunchScreen, and pointing a scene at a missing storyboard aborts. Still to do, tracked in BasedHardware#11568: AppDelegate must conform to FlutterImplicitEngineDelegate and register plugins in didInitializeImplicitFlutterEngine, and every binaryMessenger consumer must stop reading window?.rootViewController, which is nil during didFinishLaunching under scenes. AppDelegate.swift:113-114 force-unwraps that nil today, which is the SIGABRT above. Failure-Class: none Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011qmdS9crg5qFhan7PCbWvZ
ios/Runner/Info-Dev.plist is checked in, not generated-and-ignored, and the dev and raybanDat xcconfigs point INFOPLIST_FILE at it. Adding the manifest only to Info.plist therefore left a gap: `setup.sh ios` regenerates the dev plist and picks it up, but building the dev flavour straight from Xcode uses the stale tracked copy and still fails to launch. Regenerated with scripts/generate_ios_dev_info_plist.sh so the tracked file matches its source. The diff is the 19-line manifest and nothing else; NSAllowsLocalNetworking is unchanged. Worth a maintainer view: a generated file being tracked is the underlying smell here. Either it stays tracked and must be regenerated whenever Info.plist changes, or it should be gitignored and always generated. Failure-Class: none Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011qmdS9crg5qFhan7PCbWvZ
…the app delegate Second half of the UIScene migration for BasedHardware#11568. Under the UIScene lifecycle the app delegate owns no window at launch, so every binaryMessenger derived from window?.rootViewController is nil in didFinishLaunching — and registering plugins against the app delegate as the registry is itself fatal. Measured on iPhone 17 Pro / iOS 27.0 with a file-backed probe: the log line before GeneratedPluginRegistrant.register(with: self) wrote, the line after it never did, and the process died. That is why this is not merely a nil-messenger problem. Moves into didInitializeImplicitFlutterEngine(_:), sourcing the registry from engineBridge.pluginRegistry and the messenger from engineBridge.applicationRegistrar.messenger(): - GeneratedPluginRegistrant registration - OmiPhoneCallsPlugin (now via registry.registrar(forPlugin:)) - Watch/WatchRecorder, BLE, Ray-Ban Meta and phone-mic Pigeon APIs - notifyOnKill, apple_reminders, apple_health, speech, environment, audioSession, battery_widget channels and WifiNetworkPlugin No window?.rootViewController reference remains in the file, and all nine force unwraps of it are gone — each was an independent crash. Left alone deliberately: SwiftFlutterForegroundTaskPlugin's registrant callback and the top-level registerPlugins(registry:), which both receive a registry from their caller and are already scene-independent. Incidental fix: a launch carrying a deep link previously hit an early `return true` in didFinishLaunching and skipped ALL channel setup. Registration no longer shares a code path with link handling. Still outstanding for BasedHardware#11568, not in this commit: applicationWillEnterForeground never fires under scenes (measured, 0 of 4 foregrounds, while the notification fires 4 of 4), so OmiBleManager.reconnectStalePeripherals() silently stops running. That is a behavioural change and deserves its own commit. Verification: `flutter build ios --flavor dev --debug` compiles with zero Swift diagnostics. It cannot be signed on this branch — upstream/main hardcodes team 9536L8KLMP and unsuffixed bundle ids, which is what BasedHardware#7641 fixes — so runtime verification is done with those signing changes applied on top. Failure-Class: none Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011qmdS9crg5qFhan7PCbWvZ
…hook applicationWillEnterForeground never fires under the UIScene lifecycle — measured 0/4 on iPhone 17 Pro / iOS 27.0 across two full background -> reopen -> swipe-kill cycles, while the app-level UIApplication.willEnterForegroundNotification fired 4/4 in the same runs. Apple's scene-based apps get sceneWillEnterForeground instead; the delegate override silently stops running with no crash and no log. AppDelegate.swift previously called OmiBleManager.shared .reconnectStalePeripherals() from that dead override, so BLE peripherals would never reconnect when the app returns to the foreground. Moved the call to a NotificationCenter observer registered in didInitializeImplicitFlutterEngine, which runs unconditionally once per launch and isn't gated behind the deep-link early return in didFinishLaunching. applicationWillTerminate is left alone — measured to fire 2/2, so notifyOnKill and disconnectAllPeripherals() already work correctly. Part of the BasedHardware#11568 UIScene migration (step 5 of the plan). Every other app-delegate lifecycle override in this file was already accounted for: applicationWillEnterForeground and applicationWillTerminate are the only two, and both are now measured. Failure-Class: none Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Info.plist and Info-Dev.plist both set UIMainStoryboardFile = Main, but there is no Main.storyboard in ios/Runner/ — only devLaunchScreen.storyboard and prodLaunchScreen.storyboard (the built bundle contains only those two .storyboardc). The app-delegate lifecycle tolerated the dangling reference; UISceneStoryboardFile could not be set to the same nonexistent name without aborting at launch (see the manifest added in 05b3312), which is what surfaced this as worth fixing rather than leaving as a latent oddity. Removed from both plists with PlistBuddy; plutil -lint passes on each. Answers open question 3 on BasedHardware#11570. Failure-Class: none Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…ise was wrong 05b3312 omitted UISceneStoryboardFile from the scene manifest because "this project has no Main.storyboard." That was never actually true — Base.lproj/Main.storyboard exists, is a stock FlutterViewController scene, and is correctly wired into Runner.xcodeproj's PBXResourcesBuildPhase for both prod and dev targets. The false claim traces to `ls ios/Runner/*.storyboard`, which doesn't recurse into the .lproj subdirectory Xcode puts localized resources in. Omitting the key meant UIKit never auto-instantiated a root view controller for the scene on connect, so the window stayed blank — no FlutterViewController ever got created, and consequently didInitializeImplicitFlutterEngine (where all of 4e766a7's migrated plugin/channel registration lives) never fired. This was invisible to devicectl-only launch testing today, because a debug-mode Flutter build refuses to create an engine outside Xcode/Flutter tooling ("Cannot create a FlutterEngine instance in debug mode without Flutter tooling or Xcode") and silently no-ops instead of crashing — so "process alive, no crash" proved nothing about whether the app was actually rendering anything. Verified by adding the key back and rebuilding in --profile mode (launchable via plain devicectl, sidestepping the debug-mode engine restriction): a temporary probe confirmed didInitializeImplicitFlutterEngine now fires. The app then hit an unrelated crash — an invalid placeholder GOOGLE_APP_ID in the dev flavor's local GoogleService-Info.plist (a pre-existing, untracked, community-build-local-harness placeholder file, not caused by this change). Failure-Class: none Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
c6b94d3 to
d6b6cf7
Compare
Why
Apps built against the iOS 26+ SDK must adopt the UIScene lifecycle. Omi's
app/ios/Runner/Info.plisthad noUIApplicationSceneManifest, so UIKit trapped during scene setup before the Dart VM started — the app installed and signed fine, showed the launch storyboard, and stayed there permanently with no crash dialog, no UI, and no Dart code executed. Full diagnosis in #11568.iOS 27 ships publicly in about two months, so this is a deadline rather than a hypothetical.
Correction — commit 1's premise was wrong, and it hid a real bug for hours
Commit 1 omitted
UISceneStoryboardFilefrom the manifest because "this project has noMain.storyboard." That was never true.app/ios/Runner/Base.lproj/Main.storyboardexists, is a completely standard stockFlutterViewControllerscene, and is correctly wired intoRunner.xcodeproj's resource build phases for both the prod and dev targets. The false claim traces to runningls ios/Runner/*.storyboard, which doesn't recurse into the.lprojsubdirectory Xcode puts localized storyboards in — an easy, mechanical mistake, and one worth flagging so it isn't repeated: preferfind ios/Runner -iname '*.storyboard'when checking whether an Xcode resource exists.Consequence: without
UISceneStoryboardFile, UIKit never auto-instantiates a root view controller when the scene connects, so noFlutterViewControlleris ever created — and since the implicit Flutter engine is created lazily when that happens,didInitializeImplicitFlutterEngine(where commit 3's entire plugin/channel migration lives) never fired. The result was a silent blank screen, not a crash.This went undetected through the "MILESTONE" verification recorded against commits 1-3 (process alive N seconds after launch, no termination error) because that testing used
devicectl device process launchdirectly, and debug-mode Flutter builds refuse to create an engine at all outside Xcode orflutter run("Cannot create a FlutterEngine instance in debug mode without Flutter tooling or Xcode... profile and release mode apps can be launched from the home screen"). So the app never even attempted engine creation under that test — "process alive, no crash" was true, but proved nothing about whether the app was rendering anything. It happened to look identical to success.Found and fixed by rebuilding
--profileinstead of--debug(profile mode can launch from a baredevicectllaunch, unlike debug), which surfaced the real state: blank screen, confirmed via a temporary log probe thatdidInitializeImplicitFlutterEnginewas not firing. RestoringUISceneStoryboardFile = Mainfixed it — the same probe now fires. The app then reaches an unrelated crash (see below), which is itself further proof of forward progress: it wasn't reachable before this fix.What this PR does now (6 commits)
05b3312416— addsUIApplicationSceneManifesttoInfo.plist. Clears the native launch trap: the Dart VM Service becomes discoverable andDartWorker/AudioSessionthreads come up. The app then SIGABRTs, becausewindow?.rootViewControlleris nil under scenes andAppDelegate.swiftforce-unwraps it in nine places. ItsUISceneStoryboardFileomission was later found to be wrong — see commit 6.69b1d80958— carries the same manifest into the trackedInfo-Dev.plist, which the dev/raybanDat xcconfigs actually build against. Without this, a plain Xcode dev build used the stale tracked copy and still crashed.4e766a7b13— the AppDelegate migration itself.AppDelegatenow conforms toFlutterImplicitEngineDelegate; every plugin-registration andbinaryMessengerconsumer (GeneratedPluginRegistrant,OmiPhoneCallsPlugin, the Watch/BLE/Ray-Ban/phone-mic Pigeon APIs, and all seven method channels plusWifiNetworkPlugin) moved intodidInitializeImplicitFlutterEngine(_:), sourcing the registry fromengineBridge.pluginRegistryand the messenger fromengineBridge.applicationRegistrar.messenger(). Nowindow?.rootViewControllerreference remains and all nine force unwraps are gone. Incidental fix bundled in: a launch carrying a deep link previously hit an earlyreturn trueindidFinishLaunchingWithOptionsand skipped all channel setup — registration no longer shares a code path with link handling.2fe5d7e43a—applicationWillEnterForegroundnever fires under the UIScene lifecycle (measured 0/4 across two full background→reopen→swipe-kill cycles on iPhone 17 Pro / iOS 27.0), so theOmiBleManager.shared.reconnectStalePeripherals()call inside it was silently dead — no crash, no log, BLE peripherals just never reconnect on foreground. Relocated the call to aNotificationCenterobserver onUIApplication.willEnterForegroundNotification, which fired 4/4 in the same measurement, registered indidInitializeImplicitFlutterEngine(not indidFinishLaunchingWithOptions, for the same deep-link-early-return reason as above).applicationWillTerminatewas left alone — measured 2/2, already correct, sonotifyOnKillanddisconnectAllPeripherals()still work.019861ddfc— removes the danglingUIMainStoryboardFile = Mainkey from both plists (the old, pre-scene equivalent ofUISceneStoryboardFile— also dangling becausewindow?.rootViewControllerwas populated byFlutterAppDelegate's own code path, not storyboard auto-instantiation, under the old lifecycle).239c357— restoresUISceneStoryboardFile = Main, correcting commit 1. See above.Verification
Compiling proves nothing here — the whole failure class is runtime, which is why hardware verification is load-bearing for this PR specifically. Two different launch mechanisms were used and they are not equally trustworthy:
flutter run(lldb-attached)DartWorker/AudioSessionthreads live, first frame reached)devicectl device process launch, debug builddevicectl device process launch, profile buildGiven the middle row, the "alive N seconds, no termination error" evidence recorded for commits 3-5 during today's earlier debug-build testing should be read as inconclusive, not confirming — it's consistent with both "works" and "silently never started," and turned out to be the latter until commit 6. The profile-mode test after commit 6 is the first devicectl-based test in this PR that actually proves engine-level behavior:
didFinishLaunchingWithOptionsprobedidInitializeImplicitFlutterEngineprobeGOOGLE_APP_IDin the dev flavor's localGoogleService-Info.plist— an untracked, pre-existing community-build config placeholder, unrelated to this PRThe
flutter run-based measurements for commits 1-3 (SIGABRT fix, first frame reached) remain trustworthy — that mechanism doesn't have the debug-mode gap described above.Answers to the original open questions
window?.rootViewController?engineBridge.applicationRegistrar.messenger()— confirmed against the Flutter 3.44.9 engine headers and now in commit 3. NoFlutterViewControlleris needed anywhere in this file.UIApplicationSupportsMultipleScenes = falsethe intended posture? Kept off; nothing in this migration required enabling it, andFlutterSceneLifeCycleEngineRegistrationdocuments that manual scene/engine registration is only needed when multi-scene is on.UIMainStoryboardFile = Mainbe removed? Yes — done in commit 5, but this is now superseded context: that key was the old (pre-scene) equivalent ofUISceneStoryboardFile, and unlike the scene one, it really was safe to drop —FlutterAppDelegate's own pre-scene code populatedwindow/rootViewControllerprogrammatically, so nothing depended on it. The new scene-based key needed the opposite treatment — see commit 6.What's left before this is ready to merge
Everything below needs physical interaction with a device and has not been done from this side yet. It also needs a real (non-placeholder) Firebase config for the dev flavor to get past the crash described above — out of scope for this PR, tracked separately as a community-build config gap:
flutter run, for the reasons above).notifyOnKillnotification still arrives and BLE peripherals disconnect.reconnectStalePeripherals()actually runs via the new observer (the notification firing was measured with a temporary probe, not through this exact code path, and not via a debug-build devicectl launch for the reasons above).flutter runonce the local Xcode/LLDB debug-symbol issue is resolved, since that's the only mechanism proven trustworthy for this bug class.Failure-Class: none