Skip to content
Next Next commit
fix(android): re-apply edge-to-edge system bar style on appearance ch…
…ange

Core enables edge-to-edge once, in onActivityCreated, with the auto system
bar style. The activity survives a dark-mode switch (uiMode is a handled
configuration change), so the status and navigation bar icons keep the
appearance chosen for the previous theme: dark icons over the app's dark
canvas after switching to night mode.

Add `refreshEdgeToEdge`, which re-applies the activity's styling with any
colors or handler registered through the existing setters, and call it
from the Android application when the system appearance changes.
  • Loading branch information
NathanWalker committed Sep 3, 2026
commit fb9e9cdf791e2d98442991ac20356f985d949eec
15 changes: 14 additions & 1 deletion packages/core/application/application.android.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ import { androidGetForegroundActivity, androidGetStartActivity, androidSetForegr
import { getImageFetcher, getNativeApp, getRootView, initImageCache, setA11yUpdatePropertiesCallback, setApplicationPropertiesCallback, setAppMainEntry, setNativeApp, setRootView, setToggleApplicationEventListenersCallback } from './helpers-common';
import { getNativeScriptGlobals } from '../globals/global-utils';
import type { AndroidApplication as IAndroidApplication } from './application';
import { enableEdgeToEdge } from '../utils/native-helper-for-android';
import { enableEdgeToEdge, refreshEdgeToEdge } from '../utils/native-helper-for-android';
import lazy from '../utils/lazy';

declare class NativeScriptLifecycleCallbacks extends android.app.Application.ActivityLifecycleCallbacks {}
Expand Down Expand Up @@ -561,6 +561,8 @@ export class AndroidApplication extends ApplicationCommon implements IAndroidApp
this._pendingReceiverRegistrations.length = 0;
}

private edgeToEdgeAppearance: 'dark' | 'light' | null = null;

onConfigurationChanged(configuration: android.content.res.Configuration): void {
// The application context reports the app-wide configuration, which a window on a
// second display or in split-screen does not necessarily share, so the primary
Expand All @@ -572,6 +574,17 @@ export class AndroidApplication extends ApplicationCommon implements IAndroidApp
this.setLayoutDirection(primaryWindow?.layoutDirection() ?? this.getLayoutDirectionValue(configuration));
}

protected setSystemAppearance(value: 'dark' | 'light') {
super.setSystemAppearance(value);
// The activity survives the switch (uiMode is a handled config change),
// so the edge-to-edge system bar style must be applied again to pick up
// the icon appearance for the new theme.
if (this.edgeToEdgeAppearance !== value) {
this.edgeToEdgeAppearance = value;
refreshEdgeToEdge(this.foregroundActivity ?? this.startActivity);
}
}

getNativeApplication() {
let nativeApp = this.nativeApp;

Expand Down
18 changes: 18 additions & 0 deletions packages/core/utils/native-helper-for-android.ts
Original file line number Diff line number Diff line change
Expand Up @@ -310,6 +310,24 @@ interface ISystemColor {
}
const systemColors = new WeakMap<androidx.appcompat.app.AppCompatActivity, ISystemColor>();

/**
* Re-applies the activity's edge-to-edge styling. The system bar icon
* appearance is decided from the theme at the time the style is applied, so
* an activity that survives a dark-mode switch keeps the old icons until this
* runs again.
*/
export function refreshEdgeToEdge(activity: androidx.appcompat.app.AppCompatActivity): void {
if (!activity) {
return;
}
const existingColors = systemColors.get(activity);
if (existingColors) {
setEnableEdgeToEdge(activity, existingColors);
} else {
enableEdgeToEdge(activity);
}
}

function setEnableEdgeToEdge(activity: androidx.appcompat.app.AppCompatActivity, existingColors: ISystemColor) {
enableEdgeToEdge(activity, {
statusBarLightColor: existingColors.statusBarLight,
Expand Down
3 changes: 2 additions & 1 deletion packages/core/utils/native-helper.android.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import type { IOSNativeHelper } from './native-helper.types';
import { platformCheck } from './platform-check';

// importing this helper as a separate file avoids "android" symbol clash with the global android object
import { resources, collections, getWindow, getApplication, getCurrentActivity, getApplicationContext, getResources, getPackageName, getInputMethodManager, showSoftInput, dismissSoftInput, enableEdgeToEdge, setDarkModeHandler, setNavigationBarColor, setStatusBarColor, getIgnoreEdgeToEdgeOnOlderDevices, setIgnoreEdgeToEdgeOnOlderDevices } from './native-helper-for-android';
import { resources, collections, getWindow, getApplication, getCurrentActivity, getApplicationContext, getResources, getPackageName, getInputMethodManager, showSoftInput, dismissSoftInput, enableEdgeToEdge, refreshEdgeToEdge, setDarkModeHandler, setNavigationBarColor, setStatusBarColor, getIgnoreEdgeToEdgeOnOlderDevices, setIgnoreEdgeToEdgeOnOlderDevices } from './native-helper-for-android';
export { dataSerialize, dataDeserialize } from './native-helper-for-android';

export { getWindow } from './native-helper-for-android';
Expand All @@ -20,6 +20,7 @@ export const android = {
showSoftInput,
dismissSoftInput,
enableEdgeToEdge,
refreshEdgeToEdge,
setStatusBarColor,
setNavigationBarColor,
setDarkModeHandler,
Expand Down