GV

GeezerVison

The World Wide Web, restored to 800 pixels of glory.

Home|About|Gaming cabinet|Archives|How it works|Starter sites

On this site

  • Sign in
  • ActionsCI/CD and automation
  • CodespacesCloud development environments
  • All Features
  • Documentation
  • Training
  • Certifications
  • Build with Copilot
  • Customize Copilot
  • Integrations
  • Trending repos
  • GitHub Sponsors
  • Developer articles
  • Enterprises

This view

  • Original page
  • New address

GeezerVison is showing github.com — fix(android): keep the root view when an activity replaces another by herefishyfish · Pull Request #11624 · NativeScript/NativeScript

fix(android): keep the root view when an activity replaces another #11624

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

Sign up for GitHub

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
herefishyfish wants to merge 2 commits into NativeScript:main
base: main
Loading
Loading
from herefishyfish:fix/android-blank-screen-on-reopen

Uh oh!

There was an error while loading. Please reload this page.

Open

fix(android): keep the root view when an activity replaces another #11624

Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
2 commits
Select commit Hold shift + click to select a range
e1ea59b
test(core): cover a root view shared between Android activities
herefishyfish Nov 10, 2026
129b25a
fix(android): keep the root view when an activity replaces another
herefishyfish Nov 10, 2026
File filter
Filter by extension

Clear filters
Conversations
Failed to load comments.
Loading

Uh oh!

There was an error while loading. Please reload this page.

Jump to
Jump to file
Failed to load files.
Loading

Uh oh!

There was an error while loading. Please reload this page.

Diff view
Diff view

There are no files selected for viewing

154 changes: 154 additions & 0 deletions 154 packages/core/ui/frame/activity-callbacks.android.spec.ts
Show comments
View file
Open in desktop
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import { Application } from '../../application/application';
import { ActivityCallbacksImplementation } from './index.android';

vi.mock('./fragment.transitions', () => ({}));
vi.mock('./fragment', () => ({}));
vi.mock('../embedding', () => ({ isEmbedded: () => false }));

function createActivity() {
return { getIntent: () => null, setContentView: () => {} };
}

function createRootView(context: object, isLoaded = true) {
const order: string[] = [];
const view = {
_context: context,
_domId: 1,
isLoaded,
nativeViewProtected: {},
callUnloaded() {
order.push('unloaded');
view.isLoaded = false;
},
callLoaded() {
order.push('loaded');
view.isLoaded = true;
},
_setupAsRootView(activity: object) {
order.push('setup');
view._context = activity;
},
_tearDownUI: vi.fn(),
};

return { view, order };
}

function createCallbacks(rootView?: object) {
const callbacks = new ActivityCallbacksImplementation();
(callbacks as any)._rootView = rootView;

return callbacks;
}

describe('activity callbacks with a root view shared between activities', () => {
const superFunc = vi.fn();
let previousAndroid: PropertyDescriptor | undefined;

beforeEach(() => {
previousAndroid = Object.getOwnPropertyDescriptor(Application, 'android');
superFunc.mockClear();
vi.stubGlobal('org', { nativescript: { widgets: { CommonLayoutParams: class {} } } });
vi.spyOn(Application, 'createRootView').mockImplementation((view) => view);
vi.spyOn(Application, 'initRootView').mockImplementation(() => {});
Object.defineProperty(Application, 'android', {
configurable: true,
value: { _getWindowForActivity: () => undefined },
});
});

afterEach(() => {
vi.restoreAllMocks();
vi.unstubAllGlobals();
if (previousAndroid) {
Object.defineProperty(Application, 'android', previousAndroid);
} else {
delete (Application as any).android;
}
});

describe('an activity that no longer shows the root', () => {
const stale = createActivity();
const current = createActivity();

it('keeps the root loaded when it stops', () => {
const { view, order } = createRootView(current);

createCallbacks(view).onStop(stale, superFunc);

expect(order).toEqual([]);
expect(superFunc).toHaveBeenCalledTimes(1);
});

it('keeps the root intact when it is destroyed', () => {
const { view } = createRootView(current);

createCallbacks(view).onDestroy(stale, superFunc);

expect(view._tearDownUI).not.toHaveBeenCalled();
expect(superFunc).toHaveBeenCalledTimes(1);
});

it('does not save the root id for a later restore', () => {
const { view } = createRootView(current);
const outState = { putInt: vi.fn() };

createCallbacks(view).onSaveInstanceState(stale as any, outState as any, superFunc);

expect(outState.putInt).not.toHaveBeenCalled();
expect(superFunc).toHaveBeenCalledTimes(1);
});

it('takes the root back when it starts again', () => {
const { view, order } = createRootView(current);

createCallbacks(view).onStart(stale, superFunc);

expect(order).toEqual(['unloaded', 'setup', 'loaded']);
expect(view._context).toBe(stale);
});
});

describe('the activity showing the root', () => {
const showing = createActivity();

it('unloads the root when it stops', () => {
const { view, order } = createRootView(showing);

createCallbacks(view).onStop(showing, superFunc);

expect(order).toEqual(['unloaded']);
});

it('tears down the root when it is destroyed', () => {
const { view } = createRootView(showing);

createCallbacks(view).onDestroy(showing, superFunc);

expect(view._tearDownUI).toHaveBeenCalledWith(true);
});

it('loads the root without setting it up again when it starts', () => {
const { view, order } = createRootView(showing, false);

createCallbacks(view).onStart(showing, superFunc);

expect(order).toEqual(['loaded']);
});
});

it('keeps the root in a new activity that adopts it before the old one stops and is destroyed', () => {
const old = createActivity();
const next = createActivity();
const { view, order } = createRootView(old);

createCallbacks().resetActivityContent(next as any, view as any);
createCallbacks(view).onStop(old, superFunc);
createCallbacks(view).onDestroy(old, superFunc);

expect(order).toEqual(['unloaded', 'setup', 'loaded']);
expect(view._tearDownUI).not.toHaveBeenCalled();
expect(view._context).toBe(next);
});
});
18 changes: 15 additions & 3 deletions 18 packages/core/ui/frame/index.android.ts
Show comments
View file
Open in desktop
Original file line number Diff line number Diff line change
Expand Up @@ -881,7 +881,7 @@ export class ActivityCallbacksImplementation implements AndroidActivityCallbacks
@profile
public onSaveInstanceState(activity: androidx.appcompat.app.AppCompatActivity, outState: android.os.Bundle, superFunc: Function): void {
superFunc.call(activity, outState);
const rootView = this._rootView;
const rootView = this.getRootViewShownBy(activity);
if (rootView instanceof Frame) {
outState.putInt(INTENT_EXTRA, rootView.android.frameId);
rootView._saveFragmentsState();
Expand Down Expand Up @@ -926,6 +926,10 @@ export class ActivityCallbacksImplementation implements AndroidActivityCallbacks
}

const rootView = this._rootView;
if (rootView && rootView._context !== activity && !isEmbedded()) {
this.setActivityContent(activity, null, false);
}

if (rootView && !rootView.isLoaded && !isEmbedded()) {
rootView.callLoaded();
}
Expand All @@ -939,7 +943,7 @@ export class ActivityCallbacksImplementation implements AndroidActivityCallbacks
Trace.write('NativeScriptActivity.onStop();', Trace.categories.NativeLifecycle);
}

const rootView = this._rootView;
const rootView = this.getRootViewShownBy(activity);
if (rootView && rootView.isLoaded && !isEmbedded()) {
rootView.callUnloaded();
}
Expand Down Expand Up @@ -970,7 +974,7 @@ export class ActivityCallbacksImplementation implements AndroidActivityCallbacks
Trace.write('NativeScriptActivity.onDestroy();', Trace.categories.NativeLifecycle);
}

const rootView = this._rootView;
const rootView = this.getRootViewShownBy(activity);
if (rootView) {
rootView._tearDownUI(true);
}
Expand Down Expand Up @@ -1088,6 +1092,10 @@ export class ActivityCallbacksImplementation implements AndroidActivityCallbacks
Application.android.notify(resultArgs);
}

private getRootViewShownBy(activity: any): View | undefined {
return this._rootView?._context === activity ? this._rootView : undefined;
}

public resetActivityContent(activity: androidx.appcompat.app.AppCompatActivity, view?: View): void {
if (this._rootView) {
const manager = this._rootView._getFragmentManager();
Expand Down Expand Up @@ -1160,6 +1168,10 @@ export class ActivityCallbacksImplementation implements AndroidActivityCallbacks

activityRootViewsMap.set(rootView._domId, new WeakRef(rootView));

if (rootView.isLoaded && rootView._context !== activity && !isEmbedded()) {
rootView.callUnloaded();
}

// setup view as styleScopeHost
rootView._setupAsRootView(activity);

Expand Down
Loading
Oops, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

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.
You are visitor 01402503 — GeezerVison does not store the pages you look up.
Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required.