From 9dd3e1a8076e5022e411f2f2eeba34aabc68d112 Mon Sep 17 00:00:00 2001 From: Hristo Hristov Date: Mon, 15 Jan 2018 18:07:20 +0200 Subject: [PATCH] Fix crash with nested frames navigation when aactivity is recreated. We now check if frame native view is atached to window before running navigation. Livesync now recreates the main page instead of calling frame.navigate --- tns-core-modules/ui/core/view/view-common.ts | 8 ++ tns-core-modules/ui/core/view/view.android.ts | 1 + tns-core-modules/ui/core/view/view.d.ts | 10 +++ tns-core-modules/ui/frame/frame.android.ts | 77 ++++++++++++------- 4 files changed, 70 insertions(+), 26 deletions(-) diff --git a/tns-core-modules/ui/core/view/view-common.ts b/tns-core-modules/ui/core/view/view-common.ts index 25bf1f9ea..309e41f77 100644 --- a/tns-core-modules/ui/core/view/view-common.ts +++ b/tns-core-modules/ui/core/view/view-common.ts @@ -934,6 +934,14 @@ export abstract class ViewCommon extends ViewBase implements ViewDefinition { public _redrawNativeBackground(value: any): void { // } + + _onAttachedToWindow(): void { + // + } + + _onDetachedFromWindow(): void { + // + } } export const automationTextProperty = new Property({ name: "automationText" }); diff --git a/tns-core-modules/ui/core/view/view.android.ts b/tns-core-modules/ui/core/view/view.android.ts index ac3d181fc..cd4637fb8 100644 --- a/tns-core-modules/ui/core/view/view.android.ts +++ b/tns-core-modules/ui/core/view/view.android.ts @@ -164,6 +164,7 @@ function initializeDialogFragment() { const window = this.getDialog().getWindow(); const length = android.view.ViewGroup.LayoutParams.MATCH_PARENT; window.setLayout(length, length); + // This removes the default backgroundDrawable so there are no margins. window.setBackgroundDrawable(new android.graphics.drawable.ColorDrawable(android.graphics.Color.WHITE)); } diff --git a/tns-core-modules/ui/core/view/view.d.ts b/tns-core-modules/ui/core/view/view.d.ts index 058a551c3..91bc5e39b 100644 --- a/tns-core-modules/ui/core/view/view.d.ts +++ b/tns-core-modules/ui/core/view/view.d.ts @@ -666,6 +666,16 @@ export abstract class View extends ViewBase { * @param css */ _updateStyleScope(cssFileName?: string, cssString?: string, css?: string): void; + + /** + * Called in android when native view is attached to window. + */ + _onAttachedToWindow(): void; + + /** + * Called in android when native view is dettached from window. + */ + _onDetachedFromWindow(): void; //@endprivate /** diff --git a/tns-core-modules/ui/frame/frame.android.ts b/tns-core-modules/ui/frame/frame.android.ts index 63e40dc40..5967d68b1 100644 --- a/tns-core-modules/ui/frame/frame.android.ts +++ b/tns-core-modules/ui/frame/frame.android.ts @@ -28,6 +28,8 @@ const INTENT_EXTRA = "com.tns.activity"; const FRAMEID = "_frameId"; const CALLBACKS = "_callbacks"; +const ownerSymbol = Symbol("_owner"); + let navDepth = -1; let fragmentId = -1; export let moduleLoaded: boolean; @@ -38,36 +40,41 @@ if (global && global.__inspector) { devtools.attachDOMInspectorCommandCallbacks(global.__inspector); } -export function reloadPage(): void { - const frame = topmost(); - if (frame) { - if (frame.currentPage && frame.currentPage.modal) { - frame.currentPage.modal.closeModal(); - } +export let attachStateChangeListener: android.view.View.OnAttachStateChangeListener; - const currentEntry = frame._currentEntry.entry; - const newEntry: NavigationEntry = { - animated: false, - clearHistory: true, - context: currentEntry.context, - create: currentEntry.create, - moduleName: currentEntry.moduleName, - backstackVisible: currentEntry.backstackVisible - } +function getAttachListener(): android.view.View.OnAttachStateChangeListener { + if (!attachStateChangeListener) { + @Interfaces([android.view.View.OnAttachStateChangeListener]) + class AttachListener extends java.lang.Object implements android.view.View.OnAttachStateChangeListener { + constructor() { + super(); + return global.__native(this); + } - // If create returns the same page instance we can't recreate it. - // Instead of navigation set activity content. - // This could happen if current page was set in XML as a Page instance. - if (newEntry.create) { - const page = newEntry.create(); - if (page === frame.currentPage) { - resetActivityContent(frame.android.activity); - return; + onViewAttachedToWindow(view: android.view.View): void { + const owner: View = view[ownerSymbol]; + if (owner) { + owner._onAttachedToWindow(); + } + } + + onViewDetachedFromWindow(view: android.view.View): void { + const owner: View = view[ownerSymbol]; + if (owner) { + owner._onDetachedFromWindow(); + } } } - frame.navigate(newEntry); + attachStateChangeListener = new AttachListener(); } + + return attachStateChangeListener; +} + +export function reloadPage(): void { + // Delete previously cached root view in order to recreate it. + resetActivityContent(application.android.foregroundActivity); } // attach on global, so it can be overwritten in NativeScript Angular @@ -78,6 +85,7 @@ export class Frame extends FrameBase { private _delayedNavigationEntry: BackstackEntry; private _containerViewId: number = -1; private _tearDownPending = false; + private _attachedToWindow = false; public _isBack: boolean = true; constructor() { @@ -107,13 +115,24 @@ export class Frame extends FrameBase { return this._android; } + _onAttachedToWindow(): void { + super._onAttachedToWindow(); + this._attachedToWindow = true; + this._processNextNavigationEntry(); + } + + _onDetachedFromWindow(): void { + super._onDetachedFromWindow(); + this._attachedToWindow = false; + } + protected _processNextNavigationEntry(): void { // In case activity was destroyed because of back button pressed (e.g. app exit) // and application is restored from recent apps, current fragment isn't recreated. // In this case call _navigateCore in order to recreate the current fragment. // Don't call navigate because it will fire navigation events. // As JS instances are alive it is already done for the current page. - if (!this.isLoaded) { + if (!this.isLoaded || !this._attachedToWindow) { return; } @@ -328,6 +347,9 @@ export class Frame extends FrameBase { public initNativeView(): void { super.initNativeView(); + const listener = getAttachListener(); + this.nativeViewProtected.addOnAttachStateChangeListener(listener); + this.nativeViewProtected[ownerSymbol] = this; this._android.rootViewGroup = this.nativeViewProtected; if (this._containerViewId < 0) { this._containerViewId = android.view.View.generateViewId(); @@ -336,6 +358,9 @@ export class Frame extends FrameBase { } public disposeNativeView() { + const listener = getAttachListener(); + this.nativeViewProtected.removeOnAttachStateChangeListener(listener); + this.nativeViewProtected[ownerSymbol] = null; this._tearDownPending = !!this._executingEntry; const current = this._currentEntry; @@ -661,7 +686,7 @@ class FragmentCallbacksImplementation implements AndroidFragmentCallbacks { // Make sure page will have styleScope even if parents don't. page._updateStyleScope(); } - + this.frame._addView(page); }