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,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 {

View File

@ -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) {

View File

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

View File

@ -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;

View File

@ -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 {

View File

@ -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,