@@ -14,7 +14,7 @@ import {
1414
1515import {
1616 _setAndroidFragmentTransitions , _onFragmentCreateAnimator , _getAnimatedEntries ,
17- _updateTransitions , _reverseTransitions , _clearEntry , _clearFragment , AnimationType
17+ _updateTransitions , _reverseTransitions , _clearEntry , _clearFragment , AnimationType , ExpandedEntry
1818} from "./fragment.transitions" ;
1919
2020import { profile } from "../../profiling" ;
@@ -93,6 +93,7 @@ export class Frame extends FrameBase {
9393 private _tearDownPending = false ;
9494 private _attachedToWindow = false ;
9595 public _isBack : boolean = true ;
96+ private _cachedAnimationEntry : ExpandedEntry ;
9697
9798 constructor ( ) {
9899 super ( ) ;
@@ -170,6 +171,17 @@ export class Frame extends FrameBase {
170171 const entry = this . _currentEntry ;
171172 if ( entry && manager && ! manager . findFragmentByTag ( entry . fragmentTag ) ) {
172173 // Simulate first navigation (e.g. no animations or transitions)
174+ // we need to cache the original animation settings so we can restore them later; otherwise as the
175+ // simulated first navigation is not animated (it is actually a zero duration animator) the "popExit" animation
176+ // is broken when transaction.setCustomAnimations(...) is used in a scenario with:
177+ // 1) forward navigation
178+ // 2) suspend / resume app
179+ // 3) back navigation -- the exiting fragment is erroneously animated with the exit animator from the
180+ // simulated navigation (NoTransition, zero duration animator) and thus the fragment immediately disappears;
181+ // the user only sees the animation of the entering fragment as per its specific enter animation settings.
182+ // NOTE: we are restoring the animation settings in Frame.setCurrent(...) as navigation completes asynchronously
183+ this . _cachedAnimationEntry = getAnimatorState ( this . _currentEntry ) ;
184+
173185 this . _currentEntry = null ;
174186 // NavigateCore will eventually call _processNextNavigationEntry again.
175187 this . _navigateCore ( entry ) ;
@@ -194,8 +206,12 @@ export class Frame extends FrameBase {
194206 }
195207
196208 onUnloaded ( ) {
197- this . disposeCurrentFragment ( ) ;
198209 super . onUnloaded ( ) ;
210+
211+ // calling dispose fragment after super.onUnloaded() means we are not relying on the built-in Android logic
212+ // to automatically remove child fragments when parent fragment is removed;
213+ // this fixes issue with missing nested fragment on app suspend / resume;
214+ this . disposeCurrentFragment ( ) ;
199215 }
200216
201217 private disposeCurrentFragment ( ) : void {
@@ -278,6 +294,14 @@ export class Frame extends FrameBase {
278294 // Continue with next item in the queue.
279295 this . _processNextNavigationEntry ( ) ;
280296 }
297+
298+ // restore cached animation settings if we just completed simulated first navigation (no animation)
299+ if ( this . _cachedAnimationEntry ) {
300+ setAnimatorState ( this . _currentEntry , this . _cachedAnimationEntry ) ;
301+
302+ this . _cachedAnimationEntry = null ;
303+ }
304+
281305 }
282306
283307 public onBackPressed ( ) : boolean {
@@ -332,7 +356,7 @@ export class Frame extends FrameBase {
332356 const newFragmentTag = `fragment${ fragmentId } [${ navDepth } ]` ;
333357 const newFragment = this . createFragment ( newEntry , newFragmentTag ) ;
334358 const transaction = manager . beginTransaction ( ) ;
335- const animated = this . _getIsAnimatedNavigation ( newEntry . entry ) ;
359+ const animated = currentEntry ? this . _getIsAnimatedNavigation ( newEntry . entry ) : false ;
336360 // NOTE: Don't use transition for the initial navigation (same as on iOS)
337361 // On API 21+ transition won't be triggered unless there was at least one
338362 // layout pass so we will wait forever for transitionCompleted handler...
@@ -346,7 +370,7 @@ export class Frame extends FrameBase {
346370 }
347371
348372 transaction . replace ( this . containerViewId , newFragment , newFragmentTag ) ;
349- transaction . commit ( ) ;
373+ transaction . commitAllowingStateLoss ( ) ;
350374 }
351375
352376 public _goBackCore ( backstackEntry : BackstackEntry ) {
@@ -369,11 +393,12 @@ export class Frame extends FrameBase {
369393 const transitionReversed = _reverseTransitions ( backstackEntry , this . _currentEntry ) ;
370394 if ( ! transitionReversed ) {
371395 // If transition were not reversed then use animations.
372- transaction . setCustomAnimations ( AnimationType . popEnterFakeResourceId , AnimationType . popExitFakeResourceId , AnimationType . enterFakeResourceId , AnimationType . exitFakeResourceId ) ;
396+ // we do not use Android backstack so setting popEnter / popExit is meaningless (3rd and 4th optional args)
397+ transaction . setCustomAnimations ( AnimationType . popEnterFakeResourceId , AnimationType . popExitFakeResourceId ) ;
373398 }
374399
375400 transaction . replace ( this . containerViewId , backstackEntry . fragment , backstackEntry . fragmentTag ) ;
376- transaction . commit ( ) ;
401+ transaction . commitAllowingStateLoss ( ) ;
377402 }
378403
379404 public _removeEntry ( removed : BackstackEntry ) : void {
@@ -470,6 +495,27 @@ export class Frame extends FrameBase {
470495 }
471496}
472497
498+ function getAnimatorState ( entry : BackstackEntry ) : ExpandedEntry {
499+ const expandedEntry = < ExpandedEntry > entry ;
500+ const snapshot = < ExpandedEntry > { } ;
501+ snapshot . enterAnimator = expandedEntry . enterAnimator ;
502+ snapshot . exitAnimator = expandedEntry . exitAnimator ;
503+ snapshot . popEnterAnimator = expandedEntry . popEnterAnimator ;
504+ snapshot . popExitAnimator = expandedEntry . popExitAnimator ;
505+ snapshot . transitionName = expandedEntry . transitionName ;
506+
507+ return snapshot ;
508+ }
509+
510+ function setAnimatorState ( entry : BackstackEntry , snapshot : ExpandedEntry ) : void {
511+ const expandedEntry = < ExpandedEntry > entry ;
512+ expandedEntry . enterAnimator = snapshot . enterAnimator ;
513+ expandedEntry . exitAnimator = snapshot . exitAnimator ;
514+ expandedEntry . popEnterAnimator = snapshot . popEnterAnimator ;
515+ expandedEntry . popExitAnimator = snapshot . popExitAnimator ;
516+ expandedEntry . transitionName = snapshot . transitionName ;
517+ }
518+
473519function clearEntry ( entry : BackstackEntry ) : void {
474520 if ( entry . fragment ) {
475521 _clearFragment ( entry ) ;
@@ -786,16 +832,6 @@ class FragmentCallbacksImplementation implements AndroidFragmentCallbacks {
786832 traceWrite ( `${ fragment } .onDestroyView()` , traceCategories . NativeLifecycle ) ;
787833 }
788834
789- // fixes 'java.lang.IllegalStateException: The specified child already has a parent. You must call removeView() on the child's parent first'.
790- // on app resume in nested frame scenarios with support library version greater than 26.0.0
791- const view = fragment . getView ( ) ;
792- if ( view != null ) {
793- const viewParent = view . getParent ( ) ;
794- if ( viewParent instanceof android . view . ViewGroup ) {
795- viewParent . removeView ( view ) ;
796- }
797- }
798-
799835 superFunc . call ( fragment ) ;
800836 }
801837
0 commit comments