Skip to content

Commit

Permalink
LayoutAnimations.js: ensure that onComplete callback is always called…
Browse files Browse the repository at this point in the history
…, even if LayoutAnimations is disabled on iOS

Summary:
See comments. On iOS in Fabric, LayoutAnimations is only conditionally enabled; whereas on Android it's always enabled. That means for code on iOS that relies on the onComplete callback, there might be bugs.

Ensure the callback is always called on iOS by racing a timer with the animation completion.

This will be deleted before Fabric "ships" fully.

Impact is minimal since this change is scoped to only run on iOS and under Fabric.

Changelog: [Internal]

Reviewed By: sammy-SC

Differential Revision: D26166237

fbshipit-source-id: 9a07a402845c379e1511f199eef3d3e249e1eb06
  • Loading branch information
JoshuaGross authored and facebook-github-bot committed Feb 1, 2021
1 parent 1ee87e1 commit 6eea597
Showing 1 changed file with 55 additions and 18 deletions.
73 changes: 55 additions & 18 deletions Libraries/LayoutAnimation/LayoutAnimation.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,29 +26,66 @@ export type LayoutAnimationConfig = LayoutAnimationConfig_;
type OnAnimationDidEndCallback = () => void;
type OnAnimationDidFailCallback = () => void;

/**
* Configures the next commit to be animated.
*
* onAnimationDidEnd is guaranteed to be called when the animation completes.
* onAnimationDidFail is *never* called in the classic, pre-Fabric renderer,
* and never has been. In the new renderer (Fabric) it is called only if configuration
* parsing fails.
*/
function configureNext(
config: LayoutAnimationConfig,
onAnimationDidEnd?: OnAnimationDidEndCallback,
onAnimationDidFail?: OnAnimationDidFailCallback,
) {
if (!Platform.isTesting) {
if (UIManager?.configureNextLayoutAnimation) {
UIManager.configureNextLayoutAnimation(
config,
onAnimationDidEnd ?? function() {},
onAnimationDidFail ??
function() {} /* this should never be called in Non-Fabric */,
);
}
const FabricUIManager: FabricUIManagerSpec = global?.nativeFabricUIManager;
if (FabricUIManager?.configureNextLayoutAnimation) {
global?.nativeFabricUIManager?.configureNextLayoutAnimation(
config,
onAnimationDidEnd ?? function() {},
onAnimationDidFail ??
function() {} /* this will only be called if configuration fails */,
);
}
if (Platform.isTesting) {
return;
}

if (UIManager?.configureNextLayoutAnimation) {
UIManager.configureNextLayoutAnimation(
config,
onAnimationDidEnd ?? function() {},
onAnimationDidFail ??
function() {} /* this should never be called in Non-Fabric */,
);
}

// In Fabric, LayoutAnimations are unconditionally enabled for Android, and
// conditionally enabled on iOS (pending fully shipping; this is a temporary state).
const FabricUIManager: FabricUIManagerSpec = global?.nativeFabricUIManager;
if (FabricUIManager?.configureNextLayoutAnimation) {
// Since LayoutAnimations may possibly be disabled for now on iOS, we race
// a setTimeout with animation completion, in case onComplete is never called
// from native. Once LayoutAnimations unconditionally ship everywhere, we can
// delete this mechanism.
// TODO: (T65643440) remove timeout once LayoutAnimation ships on iOS.
let animationCompletionHasRun = false;
const onAnimationComplete = () => {
if (Platform.OS === 'ios') {
if (animationCompletionHasRun) {
return;
}
animationCompletionHasRun = true;
clearTimeout(raceWithAnimationId);
}
onAnimationDidEnd?.();
};
const raceWithAnimationId =
Platform.OS === 'ios'
? setTimeout(
onAnimationComplete,
(config.duration ?? 0) + 17 /* one frame + 1ms */,
)
: null;

global?.nativeFabricUIManager?.configureNextLayoutAnimation(
config,
onAnimationComplete,
onAnimationDidFail ??
function() {} /* this will only be called if configuration parsing fails */,
);
}
}

Expand Down

0 comments on commit 6eea597

Please sign in to comment.