Skip to content

Commit e1c2bb9

Browse files
committed
fix: dispose native transitions during view disposal
1 parent 67ce09a commit e1c2bb9

File tree

4 files changed

+91
-62
lines changed

4 files changed

+91
-62
lines changed

packages/core/ui/frame/fragment.transitions.android.ts

Lines changed: 68 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
// Definitions.
2-
import { NavigationType } from './frame-common';
2+
import { NavigationType, TransitionState } from './frame-common';
33
import { NavigationTransition, BackstackEntry } from '.';
44

55
// Types.
@@ -428,6 +428,16 @@ function addToWaitingQueue(entry: ExpandedEntry): void {
428428
entries.add(entry);
429429
}
430430

431+
function cloneExpandedTransitionListener(expandedTransitionListener: ExpandedTransitionListener) {
432+
if (!expandedTransitionListener) {
433+
return null;
434+
}
435+
436+
const cloneTransition = expandedTransitionListener.transition.clone();
437+
438+
return addNativeTransitionListener(expandedTransitionListener.entry, cloneTransition);
439+
}
440+
431441
function clearExitAndReenterTransitions(entry: ExpandedEntry, removeListener: boolean): void {
432442
const fragment: androidx.fragment.app.Fragment = entry.fragment;
433443
const exitListener = entry.exitTransitionListener;
@@ -469,15 +479,69 @@ function clearExitAndReenterTransitions(entry: ExpandedEntry, removeListener: bo
469479
}
470480
}
471481

482+
export function _getTransitionState(entry: ExpandedEntry): TransitionState {
483+
let transitionState: TransitionState;
484+
485+
if (entry.enterTransitionListener && entry.exitTransitionListener) {
486+
transitionState = {
487+
enterTransitionListener: cloneExpandedTransitionListener(entry.enterTransitionListener),
488+
exitTransitionListener: cloneExpandedTransitionListener(entry.exitTransitionListener),
489+
reenterTransitionListener: cloneExpandedTransitionListener(entry.reenterTransitionListener),
490+
returnTransitionListener: cloneExpandedTransitionListener(entry.returnTransitionListener),
491+
transitionName: entry.transitionName,
492+
entry,
493+
};
494+
} else {
495+
transitionState = null;
496+
}
497+
498+
return transitionState;
499+
}
500+
501+
export function _restoreTransitionState(entry: ExpandedEntry, snapshot: TransitionState): void {
502+
if (snapshot.enterTransitionListener) {
503+
entry.enterTransitionListener = snapshot.enterTransitionListener;
504+
}
505+
506+
if (snapshot.exitTransitionListener) {
507+
entry.exitTransitionListener = snapshot.exitTransitionListener;
508+
}
509+
510+
if (snapshot.reenterTransitionListener) {
511+
entry.reenterTransitionListener = snapshot.reenterTransitionListener;
512+
}
513+
514+
if (snapshot.returnTransitionListener) {
515+
entry.returnTransitionListener = snapshot.returnTransitionListener;
516+
}
517+
518+
entry.transitionName = snapshot.transitionName;
519+
}
520+
521+
export function _unsetTransitionProperties(entry: ExpandedEntry): void {
522+
entry.enterTransitionListener = null;
523+
entry.exitTransitionListener = null;
524+
entry.reenterTransitionListener = null;
525+
entry.returnTransitionListener = null;
526+
entry.enterAnimator = null;
527+
entry.exitAnimator = null;
528+
entry.popEnterAnimator = null;
529+
entry.popExitAnimator = null;
530+
entry.transition = null;
531+
entry.transitionName = null;
532+
entry.isNestedDefaultTransition = false;
533+
entry.isAnimationRunning = false;
534+
}
535+
472536
export function _clearFragment(entry: ExpandedEntry): void {
473-
clearEntry(entry, false);
537+
clearTransitions(entry, false);
474538
}
475539

476540
export function _clearEntry(entry: ExpandedEntry): void {
477-
clearEntry(entry, true);
541+
clearTransitions(entry, true);
478542
}
479543

480-
function clearEntry(entry: ExpandedEntry, removeListener: boolean): void {
544+
function clearTransitions(entry: ExpandedEntry, removeListener: boolean): void {
481545
clearExitAndReenterTransitions(entry, removeListener);
482546

483547
const fragment: androidx.fragment.app.Fragment = entry.fragment;

packages/core/ui/frame/fragment.transitions.d.ts

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { NavigationTransition, BackstackEntry } from '.';
1+
import { NavigationTransition, BackstackEntry, TransitionState } from '.';
22

33
/**
44
* @private
@@ -20,6 +20,21 @@ export function _updateTransitions(entry: BackstackEntry): void;
2020
* Reverse transitions from entry to fragment if any.
2121
*/
2222
export function _reverseTransitions(previousEntry: BackstackEntry, currentEntry: BackstackEntry): boolean;
23+
/**
24+
* @private
25+
*/
26+
export function _getTransitionState(entry: BackstackEntry): TransitionState;
27+
/**
28+
* @private
29+
*/
30+
export function _restoreTransitionState(entry: BackstackEntry, snapshot: TransitionState): void;
31+
/**
32+
* @private
33+
* Called when entry is removed from backstack (either back navigation or
34+
* navigate with clear history). Removes all animations and transitions from entry
35+
* and clears all listeners in order to prevent memory leaks.
36+
*/
37+
export function _unsetTransitionProperties(entry: BackstackEntry): void;
2338
/**
2439
* @private
2540
* Called when entry is removed from backstack (either back navigation or

packages/core/ui/frame/index.android.ts

Lines changed: 6 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import { Trace } from '../../trace';
1111
import { View } from '../core/view';
1212
import { _stack, FrameBase, NavigationType } from './frame-common';
1313

14-
import { _clearEntry, _clearFragment, _getAnimatedEntries, _reverseTransitions, _setAndroidFragmentTransitions, _updateTransitions, addNativeTransitionListener } from './fragment.transitions';
14+
import { _clearEntry, _clearFragment, _getAnimatedEntries, _getTransitionState, _restoreTransitionState, _reverseTransitions, _setAndroidFragmentTransitions, _unsetTransitionProperties, _updateTransitions, addNativeTransitionListener } from './fragment.transitions';
1515

1616
import { profile } from '../../profiling';
1717
import { android as androidUtils } from '../../utils/native-helper';
@@ -194,7 +194,7 @@ export class Frame extends FrameBase {
194194
// simulated navigation (NoTransition, zero duration animator) and thus the fragment immediately disappears;
195195
// the user only sees the animation of the entering fragment as per its specific enter animation settings.
196196
// NOTE: we are restoring the animation settings in Frame.setCurrent(...) as navigation completes asynchronously
197-
const cachedTransitionState = getTransitionState(this._currentEntry);
197+
const cachedTransitionState = _getTransitionState(this._currentEntry);
198198

199199
if (cachedTransitionState) {
200200
this._cachedTransitionState = cachedTransitionState;
@@ -359,7 +359,7 @@ export class Frame extends FrameBase {
359359

360360
// restore cached animation settings if we just completed simulated first navigation (no animation)
361361
if (this._cachedTransitionState) {
362-
restoreTransitionState(this._currentEntry, this._cachedTransitionState);
362+
_restoreTransitionState(this._currentEntry, this._cachedTransitionState);
363363
this._cachedTransitionState = null;
364364
}
365365

@@ -398,7 +398,7 @@ export class Frame extends FrameBase {
398398
// HACK: This @profile decorator creates a circular dependency
399399
// HACK: because the function parameter type is evaluated with 'typeof'
400400
@profile
401-
public _navigateCore(newEntry: any) {
401+
public _navigateCore(newEntry: BackstackEntry) {
402402
// should be (newEntry: BackstackEntry)
403403
super._navigateCore(newEntry);
404404

@@ -495,6 +495,7 @@ export class Frame extends FrameBase {
495495
if (removed.fragment) {
496496
_clearEntry(removed);
497497
}
498+
_unsetTransitionProperties(removed);
498499

499500
removed.fragment = null;
500501
removed.viewSavedState = null;
@@ -504,6 +505,7 @@ export class Frame extends FrameBase {
504505
if (entry.fragment) {
505506
_clearFragment(entry);
506507
}
508+
_unsetTransitionProperties(entry);
507509

508510
entry.recreated = false;
509511
entry.fragment = null;
@@ -616,58 +618,6 @@ export function reloadPage(context?: ModuleContext): void {
616618
// attach on global, so it can be overwritten in NativeScript Angular
617619
global.__onLiveSyncCore = Frame.reloadPage;
618620

619-
function cloneExpandedTransitionListener(expandedTransitionListener: any) {
620-
if (!expandedTransitionListener) {
621-
return null;
622-
}
623-
624-
const cloneTransition = expandedTransitionListener.transition.clone();
625-
626-
return addNativeTransitionListener(expandedTransitionListener.entry, cloneTransition);
627-
}
628-
629-
function getTransitionState(entry: BackstackEntry): TransitionState {
630-
const expandedEntry = <any>entry;
631-
632-
let transitionState: TransitionState;
633-
634-
if (expandedEntry.enterTransitionListener && expandedEntry.exitTransitionListener) {
635-
transitionState = <TransitionState>{};
636-
637-
transitionState.enterTransitionListener = cloneExpandedTransitionListener(expandedEntry.enterTransitionListener);
638-
transitionState.exitTransitionListener = cloneExpandedTransitionListener(expandedEntry.exitTransitionListener);
639-
transitionState.reenterTransitionListener = cloneExpandedTransitionListener(expandedEntry.reenterTransitionListener);
640-
transitionState.returnTransitionListener = cloneExpandedTransitionListener(expandedEntry.returnTransitionListener);
641-
transitionState.transitionName = expandedEntry.transitionName;
642-
transitionState.entry = entry;
643-
} else {
644-
transitionState = null;
645-
}
646-
647-
return transitionState;
648-
}
649-
650-
function restoreTransitionState(entry: BackstackEntry, snapshot: TransitionState): void {
651-
const expandedEntry = <any>entry;
652-
if (snapshot.enterTransitionListener) {
653-
expandedEntry.enterTransitionListener = snapshot.enterTransitionListener;
654-
}
655-
656-
if (snapshot.exitTransitionListener) {
657-
expandedEntry.exitTransitionListener = snapshot.exitTransitionListener;
658-
}
659-
660-
if (snapshot.reenterTransitionListener) {
661-
expandedEntry.reenterTransitionListener = snapshot.reenterTransitionListener;
662-
}
663-
664-
if (snapshot.returnTransitionListener) {
665-
expandedEntry.returnTransitionListener = snapshot.returnTransitionListener;
666-
}
667-
668-
expandedEntry.transitionName = snapshot.transitionName;
669-
}
670-
671621
let framesCounter = 0;
672622
const framesCache = new Array<WeakRef<AndroidFrame>>();
673623

packages/core/ui/frame/index.ios.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ export class Frame extends FrameBase {
8888
// !!! THIS PROFILE DECORATOR CREATES A CIRCULAR DEPENDENCY
8989
// !!! BECAUSE THE PARAMETER TYPE IS EVALUATED WITH TYPEOF
9090
@profile
91-
public _navigateCore(backstackEntry: any) {
91+
public _navigateCore(backstackEntry: BackstackEntry) {
9292
super._navigateCore(backstackEntry);
9393

9494
const viewController: UIViewController = backstackEntry.resolvedPage.ios;

0 commit comments

Comments
 (0)