fix(android): nested fragment disappears on parent fragment removal (#6677)

This commit is contained in:
Manol Donev
2018-12-12 14:16:10 +02:00
committed by GitHub
parent 43dddbbbc3
commit c084660d0b
6 changed files with 43 additions and 20 deletions

View File

@ -1,7 +1,7 @@
import { AndroidFragmentCallbacks, setFragmentCallbacks, setFragmentClass } from "./frame"; import { AndroidFragmentCallbacks, setFragmentCallbacks, setFragmentClass } from "./frame";
@JavaProxy("com.tns.FragmentClass") @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) // This field is updated in the frame module upon `new` (although hacky this eases the Fragment->callbacks association a lot)
private _callbacks: AndroidFragmentCallbacks; private _callbacks: AndroidFragmentCallbacks;
@ -15,8 +15,7 @@ class FragmentClass extends android.support.v4.app.Fragment {
} }
public onCreateAnimator(transit: number, enter: boolean, nextAnim: number): android.animation.Animator { public onCreateAnimator(transit: number, enter: boolean, nextAnim: number): android.animation.Animator {
let result = this._callbacks.onCreateAnimator(this, transit, enter, nextAnim, super.onCreateAnimator); return this._callbacks.onCreateAnimator(this, transit, enter, nextAnim, super.onCreateAnimator);
return result;
} }
public onStop(): void { public onStop(): void {

View File

@ -86,7 +86,20 @@ export function _setAndroidFragmentTransitions(
name = navigationTransition.name ? navigationTransition.name.toLowerCase() : ""; 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) { if (!animated) {
name = "none"; name = "none";
} else if (transition) { } else if (transition) {

View File

@ -457,8 +457,8 @@ export class FrameBase extends CustomLayoutView implements FrameDefinition {
} }
public _onRootViewReset(): void { public _onRootViewReset(): void {
this._removeFromFrameStack();
super._onRootViewReset(); super._onRootViewReset();
this._removeFromFrameStack();
} }
get _childrenCount(): number { get _childrenCount(): number {

View File

@ -209,8 +209,12 @@ export class Frame extends FrameBase {
} }
public _onRootViewReset(): void { public _onRootViewReset(): void {
this.disposeCurrentFragment();
super._onRootViewReset(); 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() { onUnloaded() {
@ -223,11 +227,6 @@ export class Frame extends FrameBase {
} }
private disposeCurrentFragment(): void { 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 || if (!this._currentEntry ||
!this._currentEntry.fragment || !this._currentEntry.fragment ||
!this._currentEntry.fragment.isAdded()) { !this._currentEntry.fragment.isAdded()) {
@ -742,7 +741,13 @@ class FragmentCallbacksImplementation implements AndroidFragmentCallbacks {
} }
@profile @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; let nextAnimString: string;
switch (nextAnim) { switch (nextAnim) {
case AnimationType.enterFakeResourceId: nextAnimString = "enter"; break; case AnimationType.enterFakeResourceId: nextAnimString = "enter"; break;

View File

@ -45,7 +45,7 @@ function initializeNativeClasses() {
return; return;
} }
class TabFragmentImplementation extends android.support.v4.app.Fragment { class TabFragmentImplementation extends org.nativescript.widgets.FragmentBase {
private tab: TabView; private tab: TabView;
private index: number; private index: number;
@ -55,7 +55,6 @@ function initializeNativeClasses() {
} }
static newInstance(tabId: number, index: number): TabFragmentImplementation { static newInstance(tabId: number, index: number): TabFragmentImplementation {
const args = new android.os.Bundle(); const args = new android.os.Bundle();
args.putInt(TABID, tabId); args.putInt(TABID, tabId);
args.putInt(INDEX, index); args.putInt(INDEX, index);
@ -79,10 +78,6 @@ function initializeNativeClasses() {
return tabItem.view.nativeViewProtected; return tabItem.view.nativeViewProtected;
} }
public onDestroyView() {
super.onDestroyView();
}
} }
const POSITION_UNCHANGED = -1; const POSITION_UNCHANGED = -1;
@ -560,8 +555,13 @@ export class TabView extends TabViewBase {
} }
public _onRootViewReset(): void { public _onRootViewReset(): void {
this.disposeCurrentFragments();
super._onRootViewReset(); 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 { private disposeCurrentFragments(): void {

View File

@ -164,6 +164,12 @@
public verticalAlignment: VerticalAlignment; public verticalAlignment: VerticalAlignment;
} }
export class FragmentBase extends android.support.v4.app.Fragment {
constructor();
public getRemovingParentFragment(): android.support.v4.app.Fragment;
}
export enum Stretch { export enum Stretch {
none, none,
aspectFill, aspectFill,