From c084660d0b6747844e102b4bd6a78c7afaaeb932 Mon Sep 17 00:00:00 2001 From: Manol Donev Date: Wed, 12 Dec 2018 14:16:10 +0200 Subject: [PATCH] fix(android): nested fragment disappears on parent fragment removal (#6677) --- tns-core-modules/ui/frame/fragment.android.ts | 7 +++---- .../ui/frame/fragment.transitions.android.ts | 15 ++++++++++++++- tns-core-modules/ui/frame/frame-common.ts | 2 +- tns-core-modules/ui/frame/frame.android.ts | 19 ++++++++++++------- .../ui/tab-view/tab-view.android.ts | 14 +++++++------- .../android/org.nativescript.widgets.d.ts | 6 ++++++ 6 files changed, 43 insertions(+), 20 deletions(-) diff --git a/tns-core-modules/ui/frame/fragment.android.ts b/tns-core-modules/ui/frame/fragment.android.ts index 86b3ccc2c..99da04835 100644 --- a/tns-core-modules/ui/frame/fragment.android.ts +++ b/tns-core-modules/ui/frame/fragment.android.ts @@ -1,10 +1,10 @@ import { AndroidFragmentCallbacks, setFragmentCallbacks, setFragmentClass } from "./frame"; @JavaProxy("com.tns.FragmentClass") -class FragmentClass extends android.support.v4.app.Fragment { +class FragmentClass extends org.nativescript.widgets.FragmentBase { // This field is updated in the frame module upon `new` (although hacky this eases the Fragment->callbacks association a lot) private _callbacks: AndroidFragmentCallbacks; - + constructor() { super(); return global.__native(this); @@ -15,8 +15,7 @@ class FragmentClass extends android.support.v4.app.Fragment { } public onCreateAnimator(transit: number, enter: boolean, nextAnim: number): android.animation.Animator { - let result = this._callbacks.onCreateAnimator(this, transit, enter, nextAnim, super.onCreateAnimator); - return result; + return this._callbacks.onCreateAnimator(this, transit, enter, nextAnim, super.onCreateAnimator); } public onStop(): void { diff --git a/tns-core-modules/ui/frame/fragment.transitions.android.ts b/tns-core-modules/ui/frame/fragment.transitions.android.ts index 43eefeeff..95d682b03 100644 --- a/tns-core-modules/ui/frame/fragment.transitions.android.ts +++ b/tns-core-modules/ui/frame/fragment.transitions.android.ts @@ -86,7 +86,20 @@ export function _setAndroidFragmentTransitions( name = navigationTransition.name ? navigationTransition.name.toLowerCase() : ""; } - let useLollipopTransition = name && (name.indexOf("slide") === 0 || name === "fade" || name === "explode") && sdkVersion() >= 21; + let useLollipopTransition = !!(name && (name.indexOf("slide") === 0 || name === "fade" || name === "explode") && sdkVersion() >= 21); + // [nested frames / fragments] force disable lollipop transitions in case nested fragments + // are detected as applying dummy animator to the nested fragment with the same duration as + // the exit animator of the removing parent fragment as a workaround for + // https://code.google.com/p/android/issues/detail?id=55228 works only if custom animations are + // used + // NOTE: this effectively means you cannot use Explode transition in nested frames scenarios as + // we have implementations only for slide, fade, and flip + if (currentFragment && + currentFragment.getChildFragmentManager() && + currentFragment.getChildFragmentManager().getFragments().toArray().length > 0) { + useLollipopTransition = false; + } + if (!animated) { name = "none"; } else if (transition) { diff --git a/tns-core-modules/ui/frame/frame-common.ts b/tns-core-modules/ui/frame/frame-common.ts index 5533f98d1..a92093ee8 100644 --- a/tns-core-modules/ui/frame/frame-common.ts +++ b/tns-core-modules/ui/frame/frame-common.ts @@ -457,8 +457,8 @@ export class FrameBase extends CustomLayoutView implements FrameDefinition { } public _onRootViewReset(): void { - this._removeFromFrameStack(); super._onRootViewReset(); + this._removeFromFrameStack(); } get _childrenCount(): number { diff --git a/tns-core-modules/ui/frame/frame.android.ts b/tns-core-modules/ui/frame/frame.android.ts index 417d817bb..1f56a8eb2 100644 --- a/tns-core-modules/ui/frame/frame.android.ts +++ b/tns-core-modules/ui/frame/frame.android.ts @@ -209,8 +209,12 @@ export class Frame extends FrameBase { } public _onRootViewReset(): void { - this.disposeCurrentFragment(); super._onRootViewReset(); + + // call this AFTER the super call to ensure descendants apply their rootview-reset logic first + // i.e. in a scenario with nested frames / frame with tabview let the descendandt cleanup the inner + // fragments first, and then cleanup the parent fragments + this.disposeCurrentFragment(); } onUnloaded() { @@ -223,11 +227,6 @@ export class Frame extends FrameBase { } private disposeCurrentFragment(): void { - // when interacting with nested fragments it seems Android is smart enough - // to automatically remove child fragments when parent fragment is removed; - // however, we must add a fragment.isAdded() guard as our logic will try to - // explicitly remove the already removed child fragment causing an - // IllegalStateException: Fragment has not been attached yet. if (!this._currentEntry || !this._currentEntry.fragment || !this._currentEntry.fragment.isAdded()) { @@ -742,7 +741,13 @@ class FragmentCallbacksImplementation implements AndroidFragmentCallbacks { } @profile - public onCreateAnimator(fragment: android.support.v4.app.Fragment, transit: number, enter: boolean, nextAnim: number, superFunc: Function): android.animation.Animator { + public onCreateAnimator(fragment: org.nativescript.widgets.FragmentBase, transit: number, enter: boolean, nextAnim: number, superFunc: Function): android.animation.Animator { + // HACK: FragmentBase class MUST handle removing nested fragment scenario to workaround + // https://code.google.com/p/android/issues/detail?id=55228 + if (!enter && fragment.getRemovingParentFragment()) { + return superFunc.call(fragment, transit, enter, nextAnim); + } + let nextAnimString: string; switch (nextAnim) { case AnimationType.enterFakeResourceId: nextAnimString = "enter"; break; diff --git a/tns-core-modules/ui/tab-view/tab-view.android.ts b/tns-core-modules/ui/tab-view/tab-view.android.ts index 8b89422d6..6c2302e63 100644 --- a/tns-core-modules/ui/tab-view/tab-view.android.ts +++ b/tns-core-modules/ui/tab-view/tab-view.android.ts @@ -45,7 +45,7 @@ function initializeNativeClasses() { return; } - class TabFragmentImplementation extends android.support.v4.app.Fragment { + class TabFragmentImplementation extends org.nativescript.widgets.FragmentBase { private tab: TabView; private index: number; @@ -55,7 +55,6 @@ function initializeNativeClasses() { } static newInstance(tabId: number, index: number): TabFragmentImplementation { - const args = new android.os.Bundle(); args.putInt(TABID, tabId); args.putInt(INDEX, index); @@ -79,10 +78,6 @@ function initializeNativeClasses() { return tabItem.view.nativeViewProtected; } - - public onDestroyView() { - super.onDestroyView(); - } } const POSITION_UNCHANGED = -1; @@ -560,8 +555,13 @@ export class TabView extends TabViewBase { } public _onRootViewReset(): void { - this.disposeCurrentFragments(); super._onRootViewReset(); + + // call this AFTER the super call to ensure descendants apply their rootview-reset logic first + // i.e. in a scenario with tab frames let the frames cleanup their fragments first, and then + // cleanup the tab fragments to avoid + // android.content.res.Resources$NotFoundException: Unable to find resource ID #0xfffffff6 + this.disposeCurrentFragments(); } private disposeCurrentFragments(): void { diff --git a/tns-platform-declarations/android/org.nativescript.widgets.d.ts b/tns-platform-declarations/android/org.nativescript.widgets.d.ts index d2e446278..ae96b44f3 100644 --- a/tns-platform-declarations/android/org.nativescript.widgets.d.ts +++ b/tns-platform-declarations/android/org.nativescript.widgets.d.ts @@ -164,6 +164,12 @@ public verticalAlignment: VerticalAlignment; } + export class FragmentBase extends android.support.v4.app.Fragment { + constructor(); + + public getRemovingParentFragment(): android.support.v4.app.Fragment; + } + export enum Stretch { none, aspectFill,