fix(core): restore application lifecycle events and window resolution under UIScene - #11373
Merged
Conversation
getWindow() is the single answer to which UIWindow NativeScript means - screen metrics, the root view controller lookup and the embedded host all read it - and it asked UIKit. UIApplication.keyWindow is deprecated, and under scenes key status can sit on any connected scene's window, including one NativeScript does not own. It now answers from the window registry first, falling back to the UIKit lookups unchanged. A detached window is skipped: it holds its UIWindow reference until a surface re-attaches, but that surface is gone.
… in scene mode Scene-based apps stopped raising the application-level lifecycle events: the per-scene calls were replaced by UIApplication notifications that do not arrive once an app adopts scenes. Even a single-window app lost them. They are now derived from the windows themselves, as Android already does with its started-activity count: the app enters the foreground when the first application-role window does and leaves it when the last one goes, and the same for active state. Windows in other roles never speak for the app, and the notification handlers stay in charge for non-scene apps.
|
View your CI Pipeline Execution ↗ for commit 2a4cdf3
💡 Verify your cache is correct by running tasks in a sandbox. Read docs ↗ ☁️ Nx Cloud last updated this comment at |
commit: |
…not per activity background/foreground was already aggregated across activities, but resume/suspend fired per activity - so with more than one window, pausing one raised 'suspend' while another was still on screen and active. They are now derived from the set of active windows, matching the iOS side: 'resume' when the first window becomes active, 'suspend' when the last one resigns. Resume is still raised from onPostResume, and only NativeScript activities take part, as before.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
PR Checklist
Three multi-window lifecycle fixes. The first two are regressions from #11181 on iOS with UIScene enabled, both from the same mistake: app-level UIKit APIs were used to derive state that only the window registry can answer correctly once an app adopts scenes. The third is the same class of defect on Android, pre-existing rather than a regression.
1. Application lifecycle events stopped firing in scene mode
What is the current behavior?
#11181 removed the blocks in
sceneDidBecomeActive/sceneDidEnterBackgroundthat raised app-levelsetInBackground()/setSuspended()for the primary scene, on the assumption thatUIApplicationDidBecomeActiveNotificationandUIApplicationDidEnterBackgroundNotificationwould drive them instead.Those notifications do not arrive for scene-based apps, so
foreground,background,resumeandsuspendare never raised onApplicationat all — including for a single-window app that simply enabled UIScene.What is the new behavior?
The events are derived from the windows themselves, the way Android already does it with its started-activity count (
application.android.ts:286-320):Windows in any other role (
carplay,externalDisplay,embedded) never speak for the app, and cannot mask the last application window leaving. Non-scene apps are unaffected: theUIApplicationnotification handlers still drive them, now explicitly gated so the two paths can never double-fire.Membership is tracked as a
Setkeyed by the window instance rather than as a counter, so a repeated or dropped scene callback cannot drift the aggregate.sceneDidDisconnectremoves the window from both sets before the close/detach branch, so a scene that disconnects while still foregrounded releases the aggregate correctly.Behavior note for reviewers
For a single window the aggregate collapses to that window, so all four events fire once per cycle in the same relative order as before. Two of them now fire one callback earlier:
foregroundsceneDidBecomeActivesceneWillEnterForegroundresumesceneDidBecomeActivesceneDidBecomeActivesuspendsceneDidEnterBackgroundsceneWillResignActivebackgroundsceneDidEnterBackgroundsceneDidEnterBackgroundThe old code raised
foreground+resumetogether andbackground+suspendtogether, conflating "entered the foreground" with "became active". Splitting them matches what Android has always done, so the platforms now agree — butforegrounddoes arrive slightly before the scene is interactive. Happy to pin both to the later callback instead if that is preferred.The other intentional difference: the removed code was gated on
isPrimary, so a secondary window never drove app state. The aggregate counts any application-role window, which is the multi-window half of the fix.2.
getWindow()asked UIKit which window NativeScript meansWhat is the current behavior?
getWindow()(utils/native-helper.ios.ts) resolves throughNativeScriptViewFactory.getKeyWindow()→UIApplication.keyWindow→windows[0].UIApplication.keyWindowis deprecated by Apple, and under scenes key status can sit on any connected scene's window — including one NativeScript does not own.It is the single answer to "which
UIWindowdoes NativeScript mean", so its consumers all inherit whatever it picks: screen metrics (platform/screen/index.ios.ts),getMainScreen(), the root view controller lookup, and the embedded host resolution.What is the new behavior?
It answers from the window registry first — the active window, then the recorded primary window — and falls back to the existing UIKit chain unchanged. One change, all consumers corrected.
A detached window is deliberately skipped. It keeps its
UIWindowreference until a surface re-attaches, but that surface is gone, so handing it out would return a torn-down window after a scene reconnect. This matches the guardApplication.ios.activeWindowalready applies.Before any window is registered both new sources are empty and the original chain runs verbatim, so startup is unchanged.
3. Android raised
resume/suspendper activityWhat is the current behavior?
On Android the
background/foregroundpair is aggregated across activities viaactivitiesCount, butresume/suspendis not —onActivityPausedraisessuspenddirectly, andonPostResumeraisesresumedirectly. With more than one window (now reachable throughApplication.openWindow()), pausing one activity raises app-levelsuspendwhile another window is still on screen and active. Same defect as (1), on the other platform.What is the new behavior?
Derived from the set of active windows, mirroring the iOS change above:
resumewhen the first window becomes active,suspendwhen the last active one resigns.Deliberately unchanged: resume is still raised from
onPostResumerather thanonActivityResumed(see the comment there and #6708), and only activities carryingisNativeScriptActivitytake part. That guard is not replaced by a role filter —onActivityCreatedregisters a window for every activity in the process, third-party ones included, so those windows have role'application'too and a role check would silently widen which activities drive suspend state.An activity with no registered window falls back to speaking for the app only while no window holds the state, so the single-window case keeps its full pair rather than silently losing it.
activitiesCountis left as-is; converting it to the same set idiom is a mechanical follow-up, not part of this fix.Tests
442 → 468 passing, across three new spec files. The scene-lifecycle specs observe the real
foreground/background/resume/suspendevents on a freshiOSApplicationand drive the realSceneDelegatemethods; they cover single-window timing, first-in/last-out with two windows, non-application roles being inert, disconnect-while-foregrounded, repeated callbacks being idempotent, and that the notification handlers are silent in scene mode but still fire for non-scene apps.No new import cycles (unchanged at android 89 / ios 88).
Not covered by CI
Device-only: that
UIApplicationDidBecomeActiveNotificationgenuinely does not arrive under scenes (the gating makes the fix correct either way, which was the point), real UIKit callback ordering across a background/foreground cycle, and screen metrics resolving against the right window with two windows on different displays.