[Android] androidOverflowInset listener is lost after the native view is recreated
Los mantenedores suelen responder en 1 día
Nadie ha tomado este issue todavía.
Evaluación
- Dificultad
- 2/5
- Tiempo estimado
- 1-3 horas
- Aptitud para principiantes
- 78/100
- Tipo de issue
- Error
- Claridad
- Bien especificado
- Estado de actividad
- Activo
- Stack tecnológico
- android, typescript
- Área
- mobile-dev
Línea de trabajo
Comienza en packages/core/ui/core/view/index.android.ts leyendo initNativeView(), disposeNativeView() y la gestión del estado del listener de insets. Reproduce el problema con apps/toolbox en Android alternando la gestión manual de insets y cambiando entre la navegación por gestos y la navegación de 3 botones. Se considera terminado cuando el handler androidOverflowInset se activa para la vista nativa recreada e informa del inset inferior actual después de cada cambio del modo de navegación.
Escrito por el modelo de indexación a partir del texto del issue.
Descripción
Issue Description
On Android, once a layout's native view is recreated, its androidOverflowInset handler never fires again. This is a runtime issue, and nothing is logged.
This affects apps that handle insets themselves: a Page or layout with androidOverflowEdge="dont-apply" and an androidOverflowInset handler that applies the insets. After the activity is recreated, for example after switching between gesture and 3-button navigation, the handler is not called anymore and the page keeps the insets it had before. In 3-button mode the bottom inset stays at the gesture bar height, so content ends up under the navigation buttons. It stays that way until the app is restarted.
Expected: the handler fires again for the new native view and receives the current insets.
Cause. View.disposeNativeView() in packages/core/ui/core/view/index.android.ts resets the touch and layout-change listener flags but not insetListenerIsSet. For the new native view, initNativeView() sees insetListenerIsSet === true and needsInsetListener === false, so it skips setInsetListener(). The new LayoutBase keeps insetListener = null, and in the OverflowEdgeDontApply branch nothing calls the handler.
Proposed fix: reset the flag in disposeNativeView() so initNativeView() attaches the listener to the new native view:
--- a/packages/core/ui/core/view/index.android.ts
+++ b/packages/core/ui/core/view/index.android.ts
@@ -860,6 +860,14 @@ export class View extends ViewCommon {
this.layoutChangeListener = null;
}
}
+ if (this.insetListenerIsSet) {
+ // Mark the listener as needed so initNativeView() attaches it to the new native view.
+ this.insetListenerIsSet = false;
+ this.needsInsetListener = true;
+ if (this.nativeViewProtected && (this.nativeViewProtected as any).setInsetListener) {
+ (this.nativeViewProtected as any).setInsetListener(null);
+ }
+ }
super.disposeNativeView();
}
Alternative, also verified with the same reproduction: attach the listener in initNativeView() when there are subscribers, the same way as the layout-change listener. This makes needsInsetListener unnecessary:
--- a/packages/core/ui/core/view/index.android.ts
+++ b/packages/core/ui/core/view/index.android.ts
@@ -601,7 +601,6 @@ export class View extends ViewCommon {
private layoutChangeListener: android.view.View.OnLayoutChangeListener;
private _rootManager: androidx.fragment.app.FragmentManager;
private insetListenerIsSet: boolean;
- private needsInsetListener: boolean;
nativeViewProtected: android.view.View;
@@ -670,9 +669,6 @@ export class View extends ViewCommon {
);
this.insetListenerIsSet = true;
}
- this.needsInsetListener = false;
- } else {
- this.needsInsetListener = true;
}
}
@@ -837,7 +833,7 @@ export class View extends ViewCommon {
this.setOnLayoutChangeListener();
}
- if (!this.insetListenerIsSet && this.needsInsetListener) {
+ if (!this.insetListenerIsSet && this.hasListeners(ViewCommon.androidOverflowInsetEvent)) {
this.setInsetListener();
}
}
@@ -860,6 +856,12 @@ export class View extends ViewCommon {
this.layoutChangeListener = null;
}
}
+ if (this.insetListenerIsSet) {
+ this.insetListenerIsSet = false;
+ if (this.nativeViewProtected && (this.nativeViewProtected as any).setInsetListener) {
+ (this.nativeViewProtected as any).setInsetListener(null);
+ }
+ }
super.disposeNativeView();
}
Both versions behave the same when the handler is added at runtime and when it is set in XML, before the native view exists. The first one is what I ship in production in my app. I'm happy to use either in the PR.
Verified that #11434 fixes this: with it applied, the same reproduction shows a new event with bottom 144px after switching to 3-button navigation and bottom 72px after switching back.
Reproduction
The apps/toolbox ete page already has manual inset handling, but its handler never updated the padding binding: it assigned this.padding before calling this.set('padding', this.padding), so set() saw no change. The diff below fixes that and shows each androidOverflowInset event under the buttons:
--- a/apps/toolbox/src/pages/ete.ts
+++ b/apps/toolbox/src/pages/ete.ts
@@ -47,6 +47,8 @@ export class ETEData extends Observable {
}
padding = '0';
+ insetEvents = 0;
+ insetInfo = 'androidOverflowInset: no events';
inset = {
top: 0,
@@ -67,8 +69,11 @@ export class ETEData extends Observable {
this.inset.left = args.inset.left;
this.inset.right = args.inset.right;
this.inset.bottom = args.inset.bottom;
- this.padding = `${this.inset.top}px ${this.inset.right}px ${this.inset.bottom}px ${this.inset.left}px`;
- this.set('padding', this.padding);
+ this.set('insetEvents', this.insetEvents + 1);
+ this.set('insetInfo', `androidOverflowInset #${this.insetEvents}: top ${this.inset.top}px, bottom ${this.inset.bottom}px`);
+ if (this.overflowManual) {
+ this.set('padding', `${this.inset.top}px ${this.inset.right}px ${this.inset.bottom}px ${this.inset.left}px`);
+ }
}
toggleManualInsets(args) {
@@ -77,7 +82,9 @@ export class ETEData extends Observable {
this.overflowManual = !this.overflowManual;
page.off(View.androidOverflowInsetEvent, this.onInset, this);
page.on(View.androidOverflowInsetEvent, this.onInset, this);
- this.overflowManual ? (this.padding = '0') : (this.padding = `${this.inset.top}px ${this.inset.right}px ${this.inset.bottom}px ${this.inset.left}px`);
+ if (!this.overflowManual) {
+ this.set('padding', '0');
+ }
page.androidOverflowEdge = this.overflowManual ? 'dont-apply' : 'none';
}
}
--- a/apps/toolbox/src/pages/ete.xml
+++ b/apps/toolbox/src/pages/ete.xml
@@ -10,6 +10,7 @@
<Button tap="{{randomColor}}" text="Update BG to a random color" />
<Button tap="{{toggleOverflowTopBottom}}" text="Toggle Overflow Top and Bottom" />
<Button tap="{{toggleManualInsets}}" text="Toggle manual overflow handling" />
+ <Label text="{{ insetInfo }}" textWrap="true" textAlignment="center" padding="16" />
</StackLayout>
</ScrollView>
</Page>
- Apply the toolbox diff above and run
apps/toolboxon Android with gesture navigation enabled. - Open ete and tap Toggle manual overflow handling. The label shows
androidOverflowInset #1: top 156px, bottom 72px. - Switch to 3-button navigation (Settings → System → Navigation mode, or
adb shell cmd overlay enable-exclusive --category com.android.internal.systemui.navbar.threebutton). This recreates the activity. - The label still shows
#1 … bottom 72px: the handler is not called, although the navigation bar is now taller. - Switch back to gesture navigation (
adb shell cmd overlay enable-exclusive --category com.android.internal.systemui.navbar.gestural). The label still shows#1.
With either fix applied, step 4 shows a new event with bottom 144px, and step 5 shows another one with bottom 72px.
Before the fix
After the fix
Relevant log output (if applicable)
Environment
Reproduced in apps/toolbox on main (a032c224c), Android 16 (API 36) emulator, sdk_gphone64_arm64.
Also reproduced in a production app on @nativescript/core 9.0.18 on a physical device.
OS: macOS 15.7.3
CPU: (10) arm64 Apple M1 Pro
Shell: /opt/homebrew/bin/fish
node: 26.10.0
npm: 11.19.1
nativescript: 9.1.1
# android
java: 17.0.20.1
ndk: Not Found
apis: 33, 34, 35, 36, 36
build_tools: 33.0.1, 34.0.0, 35.0.0, 35.0.1, 36.0.0, 36.1.0
system_images:
- android-35 | Google Play ARM 64 v8a
- android-36 | Google Play ARM 64 v8a
# ios
xcode: 26.3/17C529
cocoapods: 1.16.2
python: Not Found
python3: 3.9.6
ruby: 3.3.12
platforms:
- DriverKit 25.2
- iOS 26.2
- macOS 26.2
- tvOS 26.2
- visionOS 26.2
- watchOS 26.2
Dependencies
"dependencies": {
"@valor/nativescript-websockets": "^2.0.3",
"nativescript-theme-core": "^1.0.4"
},
"devDependencies": {
"@analogjs/vite-plugin-angular": "2.1.3",
"@angular/build": "^21.0.0",
"@angular/compiler-cli": "^21.0.0",
"@csstools/css-calc": "~2.1.2",
"@csstools/css-color-parser": "^3.0.8",
"@csstools/css-parser-algorithms": "^3.0.4",
"@csstools/css-tokenizer": "^3.0.3",
"@nativescript/hook": "^3.0.4",
"@nativescript/nx": "^22.0.0",
"@nstudio/focus": "^20.0.2",
"@nstudio/nps-i": "~2.0.0",
"@nx/devkit": "22.5.4",
"@nx/eslint-plugin": "22.5.4",
"@nx/jest": "22.5.4",
"@nx/js": "22.5.4",
"@nx/node": "22.5.4",
"@nx/plugin": "22.5.4",
"@nx/vite": "22.5.4",
"@nx/vitest": "22.5.4",
"@nx/web": "22.5.4",
"@nx/workspace": "22.5.4",
"@prettier/plugin-xml": "^3.4.1",
"@rollup/plugin-alias": "^6.0.0",
"@rollup/plugin-commonjs": "^29.0.0",
"@rollup/plugin-replace": "^6.0.3",
"@swc-node/register": "1.11.1",
"@swc/core": "1.15.8",
"@swc/helpers": "0.5.19",
"@types/jest": "30.0.0",
"@types/node": "^20.0.0",
"@types/ws": "^8.18.1",
"@typescript-eslint/eslint-plugin": "^8.46.4",
"@typescript-eslint/parser": "^8.46.4",
"@vitejs/plugin-vue": "^6.0.5",
"@vitejs/plugin-vue-jsx": "^5.1.5",
"@vitest/coverage-v8": "4.0.9",
"@vitest/ui": "4.0.9",
"@vue/compiler-sfc": "^3.5.24",
"acorn": "^8.15.0",
"acorn-stage3": "^4.0.0",
"copy-webpack-plugin": "^13.0.0",
"copyfiles": "^2.4.0",
"css": "^3.0.0",
"css-tree": "^3.1.0",
"css-what": "^6.1.0",
"dotenv": "~16.4.0",
"dotenv-webpack": "^7.0.0",
"emoji-regex": "^10.3.0",
"enhanced-resolve": "^5.18.3",
"esbuild": "^0.27.4",
"eslint": "~8.57.0",
"eslint-config-prettier": "^10.0.0",
"fork-ts-checker-webpack-plugin": "^7.0.0",
"form-data": ">=4.0.4",
"gonzales": "^1.0.7",
"husky": "^9.0.0",
"jest": "30.0.5",
"jest-environment-jsdom": "30.0.5",
"jest-util": "30.0.5",
"jiti": "2.4.2",
"jsdom": "~22.1.0",
"lint-staged": "^15.2.0",
"loader-utils": "^2.0.0 || ^3.0.0",
"module-alias": "^2.2.2",
"nativescript": "9.1.0-alpha.17",
"nativescript-typedoc-theme": "1.1.0",
"nx": "22.5.4",
"parse-css": "git+https://github.com/tabatkins/parse-css.git",
"parserlib": "^1.1.1",
"plist": "^5.0.0",
"postcss": "^8.0.0",
"postcss-import": "^16.0.0",
"postcss-loader": "^8.0.0",
"prettier": "^3.2.5",
"react-reconciler": "^0.33.0",
"sass": "^1.72.0",
"sass-loader": "^16.0.0",
"shady-css-parser": "^0.1.0",
"terser-webpack-plugin": "^5.0.0",
"tree-kill": "^1.2.2",
"ts-dedent": "^2.2.0",
"ts-jest": "29.4.5",
"ts-loader": "^9.0.0",
"ts-node": "10.9.2",
"ts-patch": "^3.0.0",
"tslib": "^2.6.0",
"typedoc": "^0.28.14",
"typescript": "5.9.3",
"vite": "^8.0.0",
"vite-plugin-solid": "^2.11.11",
"vite-plugin-static-copy": "^4.1.1",
"vitest": "4.0.9",
"vue-loader": "^15.0.0 <= 15.9.8",
"vue-tsc": "^3.2.5",
"webpack-bundle-analyzer": "^4.0.0",
"webpack-chain": "^6.0.0",
"webpack-merge": "^6.0.0",
"webpack-virtual-modules": "^0.4.0",
"zx": "^8.3.0"
}
Please accept these terms
- I have searched the existing issues as well as StackOverflow and this has not been posted before
- This is a bug report
- I agree to follow this project's Code of Conduct
- Lenguaje dominante
- TypeScript
- Estrellas
- 25.7k
- Forks
- 1.7k
- Merge medio
- 2 d 19 h
- PR fusionados (30 d)
- 27
Preparar el entorno
Primeros pasos
- Lee el issue completo y luego la guía de contribución del proyecto.
- Comenta en el issue que vas a ocuparte — evita que dos personas hagan lo mismo.
- Haz un fork del repositorio y trabaja en una rama.
- Abre un pull request que haga referencia al número del issue.
Más de NativeScript/NativeScript
-
Dificultad 2/5 1-3 horas Aptitud para principiantes 90/100
NativeScript/NativeScript#11448 ·
Los mantenedores suelen responder en 1 día
-
Dificultad 2/5 1-3 horas Aptitud para principiantes 65/100
NativeScript/NativeScript#11068 ·
Los mantenedores suelen responder en 1 día
-
bug-pending-triage
Dificultad 3/5 1-2 días Aptitud para principiantes 76/100
NativeScript/NativeScript#11467 ·
Los mantenedores suelen responder en 1 día
-
Dificultad 5/5 Más de una semana Aptitud para principiantes 25/100
NativeScript/NativeScript#11461 ·
Los mantenedores suelen responder en 1 día
-
Dificultad 4/5 3-5 días Aptitud para principiantes 68/100
NativeScript/NativeScript#11458 ·
Los mantenedores suelen responder en 1 día
Todos los issues de NativeScript/NativeScript
Issues similares
-
triage
Dificultad 1/5 Menos de una hora Aptitud para principiantes 90/100
Los mantenedores suelen responder en 1 día
-
bug
Dificultad 2/5 1-3 horas Aptitud para principiantes 68/100
mermaid-js/mermaid-live-editor#2053 ·
Los mantenedores suelen responder en 1 día
-
factory
Dificultad 2/5 1-3 horas Aptitud para principiantes 82/100
jessepollak/home#1455 ·
Los mantenedores suelen responder en 1 día
-
community first-timers-only good first issue hacktoberfest help wanted low hanging fruit up-for-grabs
Dificultad 1/5 Menos de una hora Aptitud para principiantes 95/100
lingdojo/kana-dojo#31227 · 1 comentario · 5 reacciones ·
Los mantenedores suelen responder en 1 día
-
mobile: device viewer shows dark status bar icons on its dark backdrop in light mode (Android)Abierto
Dificultad 1/5 Menos de una hora Aptitud para principiantes 92/100
appandflow/stim#1838 ·
Los mantenedores suelen responder en 1 día