diff --git a/packages/core/ui/frame/fragment.transitions.android.ts b/packages/core/ui/frame/fragment.transitions.android.ts index 434c69ce85..ab731fe73e 100644 --- a/packages/core/ui/frame/fragment.transitions.android.ts +++ b/packages/core/ui/frame/fragment.transitions.android.ts @@ -38,22 +38,22 @@ interface ExpandedAnimator extends android.animation.Animator { } export interface ExpandedEntry extends BackstackEntry { - enterTransitionListener: ExpandedTransitionListener; - exitTransitionListener: ExpandedTransitionListener; - reenterTransitionListener: ExpandedTransitionListener; - returnTransitionListener: ExpandedTransitionListener; - - enterAnimator: ExpandedAnimator; - exitAnimator: ExpandedAnimator; - popEnterAnimator: ExpandedAnimator; - popExitAnimator: ExpandedAnimator; - - transition: Transition; - transitionName: string; - frameId: number; - - isNestedDefaultTransition: boolean; - isAnimationRunning: boolean; + enterTransitionListener?: ExpandedTransitionListener; + exitTransitionListener?: ExpandedTransitionListener; + reenterTransitionListener?: ExpandedTransitionListener; + returnTransitionListener?: ExpandedTransitionListener; + + enterAnimator?: ExpandedAnimator; + exitAnimator?: ExpandedAnimator; + popEnterAnimator?: ExpandedAnimator; + popExitAnimator?: ExpandedAnimator; + + transition?: Transition; + transitionName?: string; + frameId?: number; + + isNestedDefaultTransition?: boolean; + isAnimationRunning?: boolean; } export function _setAndroidFragmentTransitions(animated: boolean, navigationTransition: NavigationTransition, currentEntry: ExpandedEntry, newEntry: ExpandedEntry, frameId: number, fragmentTransaction: androidx.fragment.app.FragmentTransaction, layoutDirection: CoreTypes.LayoutDirectionType, isNestedDefaultTransition?: boolean): void { diff --git a/packages/core/ui/frame/frame-common.ts b/packages/core/ui/frame/frame-common.ts index 33edd741c1..5dde53f4e6 100644 --- a/packages/core/ui/frame/frame-common.ts +++ b/packages/core/ui/frame/frame-common.ts @@ -12,7 +12,7 @@ import { sanitizeModuleName } from '../../utils/common'; import { profile } from '../../profiling'; import { FRAME_SYMBOL } from './frame-helpers'; import { SharedTransition } from '../transition/shared-transition'; -import { NavigationData } from '.'; +import { Frame as FrameDefinition, NavigationData } from '.'; export { NavigationType } from './frame-interfaces'; export type { AndroidActivityCallbacks, AndroidFragmentCallbacks, AndroidFrame, BackstackEntry, NavigationContext, NavigationEntry, NavigationTransition, TransitionState, ViewEntry, iOSFrame, NavigationData } from './frame-interfaces'; @@ -35,7 +35,7 @@ function buildEntryFromArgs(arg: any): NavigationEntry { } @CSSType('Frame') -export class FrameBase extends CustomLayoutView { +export class FrameBase extends CustomLayoutView implements FrameDefinition { public static navigatingToEvent = 'navigatingTo'; public static navigatedToEvent = 'navigatedTo'; diff --git a/packages/core/ui/frame/frame-helper-for-android.ts b/packages/core/ui/frame/frame-helper-for-android.ts index e6d505ede9..d50236d4e7 100644 --- a/packages/core/ui/frame/frame-helper-for-android.ts +++ b/packages/core/ui/frame/frame-helper-for-android.ts @@ -1,14 +1,14 @@ import { Trace } from '../../trace'; import { _clearEntry, _clearFragment, _getAnimatedEntries, _reverseTransitions, _setAndroidFragmentTransitions, _updateTransitions } from './fragment.transitions'; -import type { BackstackEntry } from '.'; +import type { AndroidFrame, BackstackEntry, Frame } from '.'; import { profile } from '../../profiling'; import { getNativeApp } from '../../application/helpers-common'; import { Color } from '../../color'; import type { Page } from '../page'; -import type { AndroidFrame as Frame } from '.'; +import type { ExpandedEntry } from './fragment.transitions.android'; export const FRAMEID = '_frameId'; export const CALLBACKS = '_callbacks'; -export const framesCache = new Array>(); +export const framesCache = new Array>(); export interface AndroidFragmentCallbacks { onHiddenChanged(fragment: any, hidden: boolean, superFunc: Function): void; @@ -58,7 +58,7 @@ function findPageForFragment(fragment: androidx.fragment.app.Fragment, frame: Fr export class FragmentCallbacksImplementation implements AndroidFragmentCallbacks { public frame: Frame; - public entry: BackstackEntry; + public entry: ExpandedEntry; private backgroundBitmap: android.graphics.Bitmap = null; @profile @@ -233,11 +233,14 @@ export class FragmentCallbacksImplementation implements AndroidFragmentCallbacks return null; } - // [nested frames / fragments] see https://github.com/NativeScript/NativeScript/issues/6629 - // retaining reference to a destroyed fragment here somehow causes a cryptic - // "IllegalStateException: Failure saving state: active fragment has cleared index: -1" - // in a specific mixed parent / nested frame navigation scenario - entry.fragment = null; + // Check if entry fragment is still this fragment as the destroy lifecycle might have been called due to replace + if (entry.fragment === fragment) { + // [nested frames / fragments] see https://github.com/NativeScript/NativeScript/issues/6629 + // retaining reference to a destroyed fragment here somehow causes a cryptic + // "IllegalStateException: Failure saving state: active fragment has cleared index: -1" + // in a specific mixed parent / nested frame navigation scenario + entry.fragment = null; + } const page = entry.resolvedPage; if (!page) { @@ -279,8 +282,13 @@ export class FragmentCallbacksImplementation implements AndroidFragmentCallbacks if (!owner) { return; } - if (frame._executingContext && !(owner.entry).isAnimationRunning) { - frame.setCurrent(owner.entry, frame._executingContext.navigationType); + if (!owner.entry.isAnimationRunning) { + if (frame._executingContext) { + frame.setCurrent(owner.entry, frame._executingContext.navigationType); + } else { + // Restore cached animation settings if we just completed simulated first navigation (no animation) + frame._restoreTransitionState?.(); + } } }, 0); @@ -327,7 +335,7 @@ export class FragmentCallbacksImplementation implements AndroidFragmentCallbacks export function getFrameByNumberId(frameId: number): Frame { // Find the frame for this activity. - for (let i = 0; i < framesCache.length; i++) { + for (let i = 0, length = framesCache.length; i < length; i++) { const aliveFrame = framesCache[i].get(); if (aliveFrame && aliveFrame.frameId === frameId) { return aliveFrame.owner; diff --git a/packages/core/ui/frame/index.android.ts b/packages/core/ui/frame/index.android.ts index b76a6fd80b..0114627ff9 100644 --- a/packages/core/ui/frame/index.android.ts +++ b/packages/core/ui/frame/index.android.ts @@ -1,4 +1,4 @@ -import type { AndroidActivityCallbacks, AndroidFrame as AndroidFrameDefinition, NavigationTransition, AndroidFragmentCallbacks } from '.'; +import type { AndroidActivityCallbacks, Frame as FrameDefinition, AndroidFrame as AndroidFrameDefinition, NavigationTransition } from '.'; import type { BackstackEntry } from './frame-interfaces'; import type { Page } from '../page'; import { TransitionState } from './frame-common'; @@ -6,7 +6,7 @@ import { Observable } from '../../data/observable'; import { Trace } from '../../trace'; import { View } from '../core/view'; import { _stack, FrameBase, NavigationType } from './frame-common'; -import { _clearEntry, _clearFragment, _getAnimatedEntries, _getTransitionState, _restoreTransitionState, _reverseTransitions, _setAndroidFragmentTransitions, _updateTransitions, addNativeTransitionListener } from './fragment.transitions'; +import { _clearEntry, _clearFragment, _getAnimatedEntries, _getTransitionState, _restoreTransitionState, _reverseTransitions, _setAndroidFragmentTransitions, _updateTransitions } from './fragment.transitions'; import { profile } from '../../profiling'; import { android as androidUtils } from '../../utils/native-helper'; import type { ExpandedEntry } from './fragment.transitions.android'; @@ -25,7 +25,6 @@ export { setFragmentClass } from './fragment'; const INTENT_EXTRA = 'com.tns.activity'; const ownerSymbol = Symbol('_owner'); -const isPendingDetachSymbol = Symbol('_isPendingDetach'); let navDepth = -1; let fragmentId = -1; @@ -54,12 +53,6 @@ function getAttachListener(): android.view.View.OnAttachStateChangeListener { if (owner) { owner._onDetachedFromWindow(); } - - if (view[isPendingDetachSymbol]) { - delete view[isPendingDetachSymbol]; - view.removeOnAttachStateChangeListener(this); - view[ownerSymbol] = null; - } }, }); @@ -82,7 +75,6 @@ export class Frame extends FrameBase { */ private _isReset = false; private _cachedTransitionState: TransitionState; - private _frameCreateTimeout: NodeJS.Timeout; constructor() { super(); @@ -153,7 +145,6 @@ export class Frame extends FrameBase { this._attachedToWindow = true; this._isReset = false; this._processNextNavigationEntry(); - this._ensureEntryFragment(); } _onDetachedFromWindow(): void { @@ -208,7 +199,7 @@ export class Frame extends FrameBase { if (cachedTransitionState) { this._cachedTransitionState = cachedTransitionState; this._currentEntry = null; - // NavigateCore will eventually call _processNextNavigationEntry again. + // NavigateCore will eventually call _processNextNavigationEntry again this._navigateCore(entry); this._currentEntry = entry; } else { @@ -251,51 +242,9 @@ export class Frame extends FrameBase { this._originalBackground = null; } - this._ensureEntryFragment(); super.onLoaded(); } - onUnloaded() { - super.onUnloaded(); - - if (typeof this._frameCreateTimeout === 'number') { - clearTimeout(this._frameCreateTimeout); - this._frameCreateTimeout = null; - } - } - - /** - * TODO: Check if this fragment precaution is still needed - */ - private _ensureEntryFragment(): void { - // in case the activity is "reset" using resetRootView or disposed we must wait for - // the attachedToWindow event to make the first navigation or it will crash - // https://github.com/NativeScript/NativeScript/commit/9dd3e1a8076e5022e411f2f2eeba34aabc68d112 - // though we should not do it on app "start" - // or it will create a "flash" to activity background color - if (this._isReset && !this._attachedToWindow) { - return; - } - - this._frameCreateTimeout = setTimeout(() => { - // there's a bug with nested frames where sometimes the nested fragment is not recreated at all - // so we manually check on loaded event if the fragment is not recreated and recreate it - const currentEntry = this._currentEntry || this._executingContext?.entry; - if (currentEntry) { - if (!currentEntry.fragment) { - const manager = this._getFragmentManager(); - const transaction = manager.beginTransaction(); - currentEntry.fragment = this.createFragment(currentEntry, currentEntry.fragmentTag); - _updateTransitions(currentEntry); - transaction.replace(this.containerViewId, currentEntry.fragment, currentEntry.fragmentTag); - transaction.commitAllowingStateLoss(); - } - } - - this._frameCreateTimeout = null; - }, 0); - } - private disposeCurrentFragment(): void { if (!this._currentEntry || !this._currentEntry.fragment || !this._currentEntry.fragment.isAdded()) { return; @@ -378,11 +327,8 @@ export class Frame extends FrameBase { this._processNextNavigationEntry(); } - // restore cached animation settings if we just completed simulated first navigation (no animation) - if (this._cachedTransitionState) { - _restoreTransitionState(this._cachedTransitionState); - this._cachedTransitionState = null; - } + // Restore cached animation settings if we just completed simulated first navigation (no animation) + this._restoreTransitionState(); // restore original fragment transitions if we just completed replace navigation (hmr) if (navigationType === NavigationType.replace) { @@ -463,7 +409,7 @@ export class Frame extends FrameBase { // layout pass so we will wait forever for transitionCompleted handler... // https://github.com/NativeScript/NativeScript/issues/4895 let navigationTransition: NavigationTransition; - if (this._currentEntry) { + if (currentEntry) { navigationTransition = this._getNavigationTransition(newEntry.entry); } else { navigationTransition = null; @@ -562,13 +508,13 @@ export class Frame extends FrameBase { const nativeView = this.nativeViewProtected as android.view.ViewGroup; const listener = getAttachListener(); - // There are cases like root view when detach listener is not called upon removing view from view-tree - // so mark those views as pending and remove listener once the view is detached - if (nativeView.isAttachedToWindow()) { - nativeView[isPendingDetachSymbol] = true; - } else { - nativeView.removeOnAttachStateChangeListener(listener); - nativeView[ownerSymbol] = null; + nativeView.removeOnAttachStateChangeListener(listener); + nativeView[ownerSymbol] = null; + + // There are cases like root view when detach listener is not called before the native view gets disposed + // so call detach method directly for these views + if (this._attachedToWindow) { + this._onDetachedFromWindow(); } this._tearDownPending = !!this._executingContext; @@ -628,6 +574,13 @@ export class Frame extends FrameBase { } } + public _restoreTransitionState(): void { + if (this._cachedTransitionState) { + _restoreTransitionState(this._cachedTransitionState); + this._cachedTransitionState = null; + } + } + public _saveFragmentsState(): void { // We save only fragments in backstack. // Current fragment is saved by FragmentManager. @@ -655,12 +608,12 @@ let framesCounter = 0; class AndroidFrame extends Observable implements AndroidFrameDefinition { public rootViewGroup: android.view.ViewGroup; - public frameId; + public readonly frameId: number; private _showActionBar = true; - private _owner: Frame; + private readonly _owner: FrameDefinition; - constructor(owner: Frame) { + constructor(owner: FrameDefinition) { super(); this._owner = owner; this.frameId = framesCounter++; @@ -730,7 +683,7 @@ class AndroidFrame extends Observable implements AndroidFrameDefinition { return undefined; } - public get owner(): Frame { + public get owner(): FrameDefinition { return this._owner; } @@ -1068,6 +1021,8 @@ export class ActivityCallbacksImplementation implements AndroidActivityCallbacks const manager = this._rootView._getFragmentManager(); manager.executePendingTransactions(); + // Some flavors reuse the same root view, so unload the view in order to load it successfully when needed + this._rootView.callUnloaded(); this._rootView._onRootViewReset(); } // Delete previously cached root view in order to recreate it. diff --git a/packages/core/ui/frame/index.d.ts b/packages/core/ui/frame/index.d.ts index 7e919904b5..41796031f2 100644 --- a/packages/core/ui/frame/index.d.ts +++ b/packages/core/ui/frame/index.d.ts @@ -46,11 +46,6 @@ export class Frame extends FrameBase { */ _originalBackground?: any; - /** - * @private - */ - _saveFragmentsState?(); - /** * Gets a frame by id. */ @@ -174,13 +169,13 @@ export class Frame extends FrameBase { * * @nsProperty */ - iosNavigationBarClass: any; + iosNavigationBarClass?: any; /** * Specify a custom UIToolbar class (iOS only) * * @nsProperty */ - iosToolBarClass: any; + iosToolbarClass?: any; //@private /** @@ -269,6 +264,14 @@ export class Frame extends FrameBase { * @private */ _removeFromFrameStack(); + /** + * @private + */ + _restoreTransitionState?(); + /** + * @private + */ + _saveFragmentsState?(); //@endprivate /** @@ -456,7 +459,8 @@ export interface NavigationTransition { * To start a new Activity, a new Frame instance should be created and navigated to the desired Page. */ export interface AndroidFrame extends Observable { - frameId?: any; + frameId: number; + owner: Frame; /** * Gets the native [android ViewGroup](http://developer.android.com/reference/android/view/ViewGroup.html) instance that represents the root layout part of the Frame. @@ -488,19 +492,6 @@ export interface AndroidFrame extends Observable { * @param page The Page instance to search for. */ fragmentForPage(entry: BackstackEntry): any; - - // common properties - _resolvedPage?: Page; - _currentEntry?: BackstackEntry; - _executingContext?: NavigationContext; - _inheritStyles?(page: Page): void; - isLoaded?: boolean; - _styleScope?: any; - _addView?(view: View): void; - nativeViewProtected?: any /* android.view.View */; - _originalBackground?: any /* android.graphics.drawable.Drawable */; - backgroundColor?: any; - owner?: any; } export interface AndroidActivityCallbacks { diff --git a/packages/core/ui/frame/index.ios.ts b/packages/core/ui/frame/index.ios.ts index 9abbef1937..29fd275ff4 100644 --- a/packages/core/ui/frame/index.ios.ts +++ b/packages/core/ui/frame/index.ios.ts @@ -29,8 +29,8 @@ let navControllerDelegate: UINavigationControllerDelegate = null; export class Frame extends FrameBase { viewController: UINavigationControllerImpl; - iosNavigationBarClass: typeof NSObject; - iosToolbarClass: typeof NSObject; + iosNavigationBarClass?: typeof NSObject; + iosToolbarClass?: typeof NSObject; private _ios: iOSFrame;