Native Animated - Restore default values when removing props on Android

Summary:
Same as #11819 but for Android. I didn't notice the bug initially in my app because I was using different animations on Android which did not trigger this issue.

**Test plan**
Created a simple repro example and tested that this fixes it. https://gist.github.com/janicduplessis/0f3eb362dae63fedf99a0d3ee041796a
Closes https://github.com/facebook/react-native/pull/12842

Differential Revision: D5556439

Pulled By: hramos

fbshipit-source-id: d13f4ad258d03cca46c793751ebc49d942b99152
This commit is contained in:
Janic Duplessis
2017-08-04 13:46:39 -07:00
committed by Facebook Github Bot
parent b4f91be647
commit ac43548063
10 changed files with 352 additions and 72 deletions

View File

@@ -17,17 +17,20 @@ import com.facebook.infer.annotation.Assertions;
import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.Callback;
import com.facebook.react.bridge.LifecycleEventListener;
import com.facebook.react.bridge.OnBatchCompleteListener;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.common.annotations.VisibleForTesting;
import com.facebook.react.module.annotations.ReactModule;
import com.facebook.react.modules.core.DeviceEventManagerModule;
import com.facebook.react.modules.core.ReactChoreographer;
import com.facebook.react.uimanager.GuardedFrameCallback;
import com.facebook.react.uimanager.NativeViewHierarchyManager;
import com.facebook.react.uimanager.UIBlock;
import com.facebook.react.uimanager.UIManagerModule;
import com.facebook.react.uimanager.UIManagerModuleListener;
/**
* Module that exposes interface for creating and managing animated nodes on the "native" side.
@@ -72,7 +75,7 @@ import com.facebook.react.uimanager.UIManagerModule;
*/
@ReactModule(name = NativeAnimatedModule.NAME)
public class NativeAnimatedModule extends ReactContextBaseJavaModule implements
OnBatchCompleteListener, LifecycleEventListener {
LifecycleEventListener, UIManagerModuleListener {
protected static final String NAME = "NativeAnimatedModule";
@@ -80,11 +83,10 @@ public class NativeAnimatedModule extends ReactContextBaseJavaModule implements
void execute(NativeAnimatedNodesManager animatedNodesManager);
}
private final Object mOperationsCopyLock = new Object();
private final GuardedFrameCallback mAnimatedFrameCallback;
private final ReactChoreographer mReactChoreographer;
private ArrayList<UIThreadOperation> mOperations = new ArrayList<>();
private volatile @Nullable ArrayList<UIThreadOperation> mReadyOperations = null;
private ArrayList<UIThreadOperation> mPreOperations = new ArrayList<>();
private @Nullable NativeAnimatedNodesManager mNodesManager;
@@ -95,26 +97,9 @@ public class NativeAnimatedModule extends ReactContextBaseJavaModule implements
mAnimatedFrameCallback = new GuardedFrameCallback(reactContext) {
@Override
protected void doFrameGuarded(final long frameTimeNanos) {
if (mNodesManager == null) {
UIManagerModule uiManager = getReactApplicationContext()
.getNativeModule(UIManagerModule.class);
mNodesManager = new NativeAnimatedNodesManager(uiManager);
}
ArrayList<UIThreadOperation> operations;
synchronized (mOperationsCopyLock) {
operations = mReadyOperations;
mReadyOperations = null;
}
if (operations != null) {
for (int i = 0, size = operations.size(); i < size; i++) {
operations.get(i).execute(mNodesManager);
}
}
if (mNodesManager.hasActiveAnimations()) {
mNodesManager.runUpdates(frameTimeNanos);
NativeAnimatedNodesManager nodesManager = getNodesManager();
if (nodesManager.hasActiveAnimations()) {
nodesManager.runUpdates(frameTimeNanos);
}
// TODO: Would be great to avoid adding this callback in case there are no active animations
@@ -130,7 +115,10 @@ public class NativeAnimatedModule extends ReactContextBaseJavaModule implements
@Override
public void initialize() {
getReactApplicationContext().addLifecycleEventListener(this);
ReactApplicationContext reactCtx = getReactApplicationContext();
UIManagerModule uiManager = reactCtx.getNativeModule(UIManagerModule.class);
reactCtx.addLifecycleEventListener(this);
uiManager.addUIManagerListener(this);
}
@Override
@@ -139,24 +127,32 @@ public class NativeAnimatedModule extends ReactContextBaseJavaModule implements
}
@Override
public void onBatchComplete() {
// Note: The order of executing onBatchComplete handler (especially in terms of onBatchComplete
// from the UIManagerModule) doesn't matter as we only enqueue operations for the UI thread to
// be executed from here. Thanks to ReactChoreographer all the operations from here are going
// to be executed *after* all the operations enqueued by UIManager as the callback type that we
// use for ReactChoreographer (CallbackType.NATIVE_ANIMATED_MODULE) is run after callbacks that
// UIManager uses.
ArrayList<UIThreadOperation> operations = mOperations.isEmpty() ? null : mOperations;
if (operations != null) {
mOperations = new ArrayList<>();
synchronized (mOperationsCopyLock) {
if (mReadyOperations == null) {
mReadyOperations = operations;
} else {
mReadyOperations.addAll(operations);
public void willDispatchViewUpdates(final UIManagerModule uiManager) {
if (mOperations.isEmpty() && mPreOperations.isEmpty()) {
return;
}
final ArrayList<UIThreadOperation> preOperations = mPreOperations;
final ArrayList<UIThreadOperation> operations = mOperations;
mPreOperations = new ArrayList<>();
mOperations = new ArrayList<>();
uiManager.prependUIBlock(new UIBlock() {
@Override
public void execute(NativeViewHierarchyManager nativeViewHierarchyManager) {
NativeAnimatedNodesManager nodesManager = getNodesManager();
for (UIThreadOperation operation : preOperations) {
operation.execute(nodesManager);
}
}
}
});
uiManager.addUIBlock(new UIBlock() {
@Override
public void execute(NativeViewHierarchyManager nativeViewHierarchyManager) {
NativeAnimatedNodesManager nodesManager = getNodesManager();
for (UIThreadOperation operation : operations) {
operation.execute(nodesManager);
}
}
});
}
@Override
@@ -174,6 +170,15 @@ public class NativeAnimatedModule extends ReactContextBaseJavaModule implements
return NAME;
}
private NativeAnimatedNodesManager getNodesManager() {
if (mNodesManager == null) {
UIManagerModule uiManager = getReactApplicationContext().getNativeModule(UIManagerModule.class);
mNodesManager = new NativeAnimatedNodesManager(uiManager);
}
return mNodesManager;
}
private void clearFrameCallback() {
Assertions.assertNotNull(mReactChoreographer).removeFrameCallback(
ReactChoreographer.CallbackType.NATIVE_ANIMATED_MODULE,
@@ -186,6 +191,11 @@ public class NativeAnimatedModule extends ReactContextBaseJavaModule implements
mAnimatedFrameCallback);
}
@VisibleForTesting
public void setNodesManager(NativeAnimatedNodesManager nodesManager) {
mNodesManager = nodesManager;
}
@ReactMethod
public void createAnimatedNode(final int tag, final ReadableMap config) {
mOperations.add(new UIThreadOperation() {
@@ -336,6 +346,12 @@ public class NativeAnimatedModule extends ReactContextBaseJavaModule implements
@ReactMethod
public void disconnectAnimatedNodeFromView(final int animatedNodeTag, final int viewTag) {
mPreOperations.add(new UIThreadOperation() {
@Override
public void execute(NativeAnimatedNodesManager animatedNodesManager) {
animatedNodesManager.restoreDefaultValues(animatedNodeTag, viewTag);
}
});
mOperations.add(new UIThreadOperation() {
@Override
public void execute(NativeAnimatedNodesManager animatedNodesManager) {