@@ -24,6 +24,14 @@ import { createViewFromEntry } from "../builder";
2424
2525export * from "./frame-common" ;
2626
27+ interface AnimatorState {
28+ enterAnimator : android . animation . Animator ;
29+ exitAnimator : android . animation . Animator ;
30+ popEnterAnimator : android . animation . Animator ;
31+ popExitAnimator : android . animation . Animator ;
32+ transitionName : string ;
33+ }
34+
2735const INTENT_EXTRA = "com.tns.activity" ;
2836const ROOT_VIEW_ID_EXTRA = "com.tns.activity.rootViewId" ;
2937const FRAMEID = "_frameId" ;
@@ -93,6 +101,7 @@ export class Frame extends FrameBase {
93101 private _tearDownPending = false ;
94102 private _attachedToWindow = false ;
95103 public _isBack : boolean = true ;
104+ private _cachedAnimatorState : AnimatorState ;
96105
97106 constructor ( ) {
98107 super ( ) ;
@@ -170,6 +179,17 @@ export class Frame extends FrameBase {
170179 const entry = this . _currentEntry ;
171180 if ( entry && manager && ! manager . findFragmentByTag ( entry . fragmentTag ) ) {
172181 // Simulate first navigation (e.g. no animations or transitions)
182+ // we need to cache the original animation settings so we can restore them later; otherwise as the
183+ // simulated first navigation is not animated (it is actually a zero duration animator) the "popExit" animation
184+ // is broken when transaction.setCustomAnimations(...) is used in a scenario with:
185+ // 1) forward navigation
186+ // 2) suspend / resume app
187+ // 3) back navigation -- the exiting fragment is erroneously animated with the exit animator from the
188+ // simulated navigation (NoTransition, zero duration animator) and thus the fragment immediately disappears;
189+ // the user only sees the animation of the entering fragment as per its specific enter animation settings.
190+ // NOTE: we are restoring the animation settings in Frame.setCurrent(...) as navigation completes asynchronously
191+ this . _cachedAnimatorState = getAnimatorState ( this . _currentEntry ) ;
192+
173193 this . _currentEntry = null ;
174194 // NavigateCore will eventually call _processNextNavigationEntry again.
175195 this . _navigateCore ( entry ) ;
@@ -194,8 +214,12 @@ export class Frame extends FrameBase {
194214 }
195215
196216 onUnloaded ( ) {
197- this . disposeCurrentFragment ( ) ;
198217 super . onUnloaded ( ) ;
218+
219+ // calling dispose fragment after super.onUnloaded() means we are not relying on the built-in Android logic
220+ // to automatically remove child fragments when parent fragment is removed;
221+ // this fixes issue with missing nested fragment on app suspend / resume;
222+ this . disposeCurrentFragment ( ) ;
199223 }
200224
201225 private disposeCurrentFragment ( ) : void {
@@ -278,6 +302,14 @@ export class Frame extends FrameBase {
278302 // Continue with next item in the queue.
279303 this . _processNextNavigationEntry ( ) ;
280304 }
305+
306+ // restore cached animation settings if we just completed simulated first navigation (no animation)
307+ if ( this . _cachedAnimatorState ) {
308+ restoreAnimatorState ( this . _currentEntry , this . _cachedAnimatorState ) ;
309+
310+ this . _cachedAnimatorState = null ;
311+ }
312+
281313 }
282314
283315 public onBackPressed ( ) : boolean {
@@ -332,7 +364,7 @@ export class Frame extends FrameBase {
332364 const newFragmentTag = `fragment${ fragmentId } [${ navDepth } ]` ;
333365 const newFragment = this . createFragment ( newEntry , newFragmentTag ) ;
334366 const transaction = manager . beginTransaction ( ) ;
335- const animated = this . _getIsAnimatedNavigation ( newEntry . entry ) ;
367+ const animated = currentEntry ? this . _getIsAnimatedNavigation ( newEntry . entry ) : false ;
336368 // NOTE: Don't use transition for the initial navigation (same as on iOS)
337369 // On API 21+ transition won't be triggered unless there was at least one
338370 // layout pass so we will wait forever for transitionCompleted handler...
@@ -346,7 +378,7 @@ export class Frame extends FrameBase {
346378 }
347379
348380 transaction . replace ( this . containerViewId , newFragment , newFragmentTag ) ;
349- transaction . commit ( ) ;
381+ transaction . commitAllowingStateLoss ( ) ;
350382 }
351383
352384 public _goBackCore ( backstackEntry : BackstackEntry ) {
@@ -369,11 +401,12 @@ export class Frame extends FrameBase {
369401 const transitionReversed = _reverseTransitions ( backstackEntry , this . _currentEntry ) ;
370402 if ( ! transitionReversed ) {
371403 // If transition were not reversed then use animations.
372- transaction . setCustomAnimations ( AnimationType . popEnterFakeResourceId , AnimationType . popExitFakeResourceId , AnimationType . enterFakeResourceId , AnimationType . exitFakeResourceId ) ;
404+ // we do not use Android backstack so setting popEnter / popExit is meaningless (3rd and 4th optional args)
405+ transaction . setCustomAnimations ( AnimationType . popEnterFakeResourceId , AnimationType . popExitFakeResourceId ) ;
373406 }
374407
375408 transaction . replace ( this . containerViewId , backstackEntry . fragment , backstackEntry . fragmentTag ) ;
376- transaction . commit ( ) ;
409+ transaction . commitAllowingStateLoss ( ) ;
377410 }
378411
379412 public _removeEntry ( removed : BackstackEntry ) : void {
@@ -470,6 +503,27 @@ export class Frame extends FrameBase {
470503 }
471504}
472505
506+ function getAnimatorState ( entry : BackstackEntry ) : AnimatorState {
507+ const expandedEntry = < any > entry ;
508+ const animatorState = < AnimatorState > { } ;
509+ animatorState . enterAnimator = expandedEntry . enterAnimator ;
510+ animatorState . exitAnimator = expandedEntry . exitAnimator ;
511+ animatorState . popEnterAnimator = expandedEntry . popEnterAnimator ;
512+ animatorState . popExitAnimator = expandedEntry . popExitAnimator ;
513+ animatorState . transitionName = expandedEntry . transitionName ;
514+
515+ return animatorState ;
516+ }
517+
518+ function restoreAnimatorState ( entry : BackstackEntry , snapshot : AnimatorState ) : void {
519+ const expandedEntry = < any > entry ;
520+ expandedEntry . enterAnimator = snapshot . enterAnimator ;
521+ expandedEntry . exitAnimator = snapshot . exitAnimator ;
522+ expandedEntry . popEnterAnimator = snapshot . popEnterAnimator ;
523+ expandedEntry . popExitAnimator = snapshot . popExitAnimator ;
524+ expandedEntry . transitionName = snapshot . transitionName ;
525+ }
526+
473527function clearEntry ( entry : BackstackEntry ) : void {
474528 if ( entry . fragment ) {
475529 _clearFragment ( entry ) ;
@@ -786,16 +840,6 @@ class FragmentCallbacksImplementation implements AndroidFragmentCallbacks {
786840 traceWrite ( `${ fragment } .onDestroyView()` , traceCategories . NativeLifecycle ) ;
787841 }
788842
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-
799843 superFunc . call ( fragment ) ;
800844 }
801845
0 commit comments