diff --git a/ReactAndroid/src/main/java/com/facebook/react/animated/NativeAnimatedModule.java b/ReactAndroid/src/main/java/com/facebook/react/animated/NativeAnimatedModule.java index 0591f5dbb..f03cc8a46 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/animated/NativeAnimatedModule.java +++ b/ReactAndroid/src/main/java/com/facebook/react/animated/NativeAnimatedModule.java @@ -17,20 +17,17 @@ 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. @@ -75,7 +72,7 @@ import com.facebook.react.uimanager.UIManagerModuleListener; */ @ReactModule(name = NativeAnimatedModule.NAME) public class NativeAnimatedModule extends ReactContextBaseJavaModule implements - LifecycleEventListener, UIManagerModuleListener { + OnBatchCompleteListener, LifecycleEventListener { protected static final String NAME = "NativeAnimatedModule"; @@ -83,10 +80,11 @@ 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 mOperations = new ArrayList<>(); - private ArrayList mPreOperations = new ArrayList<>(); + private volatile @Nullable ArrayList mReadyOperations = null; private @Nullable NativeAnimatedNodesManager mNodesManager; @@ -97,9 +95,26 @@ public class NativeAnimatedModule extends ReactContextBaseJavaModule implements mAnimatedFrameCallback = new GuardedFrameCallback(reactContext) { @Override protected void doFrameGuarded(final long frameTimeNanos) { - NativeAnimatedNodesManager nodesManager = getNodesManager(); - if (nodesManager.hasActiveAnimations()) { - nodesManager.runUpdates(frameTimeNanos); + if (mNodesManager == null) { + UIManagerModule uiManager = getReactApplicationContext() + .getNativeModule(UIManagerModule.class); + mNodesManager = new NativeAnimatedNodesManager(uiManager); + } + + ArrayList 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); } // TODO: Would be great to avoid adding this callback in case there are no active animations @@ -115,10 +130,7 @@ public class NativeAnimatedModule extends ReactContextBaseJavaModule implements @Override public void initialize() { - ReactApplicationContext reactCtx = getReactApplicationContext(); - UIManagerModule uiManager = reactCtx.getNativeModule(UIManagerModule.class); - reactCtx.addLifecycleEventListener(this); - uiManager.addUIManagerListener(this); + getReactApplicationContext().addLifecycleEventListener(this); } @Override @@ -127,32 +139,24 @@ public class NativeAnimatedModule extends ReactContextBaseJavaModule implements } @Override - public void willDispatchViewUpdates(final UIManagerModule uiManager) { - if (mOperations.isEmpty() && mPreOperations.isEmpty()) { - return; + 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 operations = mOperations.isEmpty() ? null : mOperations; + if (operations != null) { + mOperations = new ArrayList<>(); + synchronized (mOperationsCopyLock) { + if (mReadyOperations == null) { + mReadyOperations = operations; + } else { + mReadyOperations.addAll(operations); + } + } } - final ArrayList preOperations = mPreOperations; - final ArrayList 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 @@ -170,15 +174,6 @@ 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, @@ -191,11 +186,6 @@ 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() { @@ -346,12 +336,6 @@ 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) { diff --git a/ReactAndroid/src/main/java/com/facebook/react/animated/NativeAnimatedNodesManager.java b/ReactAndroid/src/main/java/com/facebook/react/animated/NativeAnimatedNodesManager.java index 8f9694c44..9ed40b823 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/animated/NativeAnimatedNodesManager.java +++ b/ReactAndroid/src/main/java/com/facebook/react/animated/NativeAnimatedNodesManager.java @@ -92,7 +92,7 @@ import javax.annotation.Nullable; } else if ("value".equals(type)) { node = new ValueAnimatedNode(config); } else if ("props".equals(type)) { - node = new PropsAnimatedNode(config, this, mUIImplementation); + node = new PropsAnimatedNode(config, this); } else if ("interpolation".equals(type)) { node = new InterpolationAnimatedNode(config); } else if ("addition".equals(type)) { @@ -289,7 +289,11 @@ import javax.annotation.Nullable; "of type " + PropsAnimatedNode.class.getName()); } PropsAnimatedNode propsAnimatedNode = (PropsAnimatedNode) node; - propsAnimatedNode.connectToView(viewTag); + if (propsAnimatedNode.mConnectedViewTag != -1) { + throw new JSApplicationIllegalArgumentException("Animated node " + animatedNodeTag + " is " + + "already attached to a view"); + } + propsAnimatedNode.mConnectedViewTag = viewTag; mUpdatedNodes.put(animatedNodeTag, node); } @@ -304,24 +308,11 @@ import javax.annotation.Nullable; "of type " + PropsAnimatedNode.class.getName()); } PropsAnimatedNode propsAnimatedNode = (PropsAnimatedNode) node; - propsAnimatedNode.disconnectFromView(viewTag); - } - - public void restoreDefaultValues(int animatedNodeTag, int viewTag) { - AnimatedNode node = mAnimatedNodes.get(animatedNodeTag); - // Restoring default values needs to happen before UIManager operations so it is - // possible the node hasn't been created yet if it is being connected and - // disconnected in the same batch. In that case we don't need to restore - // default values since it will never actually update the view. - if (node == null) { - return; + if (propsAnimatedNode.mConnectedViewTag != viewTag) { + throw new JSApplicationIllegalArgumentException("Attempting to disconnect view that has " + + "not been connected with the given animated node"); } - if (!(node instanceof PropsAnimatedNode)) { - throw new JSApplicationIllegalArgumentException("Animated node connected to view should be" + - "of type " + PropsAnimatedNode.class.getName()); - } - PropsAnimatedNode propsAnimatedNode = (PropsAnimatedNode) node; - propsAnimatedNode.restoreDefaultValues(); + propsAnimatedNode.mConnectedViewTag = -1; } public void addAnimatedEventToView(int viewTag, String eventName, ReadableMap eventMapping) { @@ -522,7 +513,7 @@ import javax.annotation.Nullable; if (nextNode instanceof PropsAnimatedNode) { // Send property updates to native view manager try { - ((PropsAnimatedNode) nextNode).updateView(); + ((PropsAnimatedNode) nextNode).updateView(mUIImplementation); } catch (IllegalViewOperationException e) { // An exception is thrown if the view hasn't been created yet. This can happen because views are // created in batches. If this particular view didn't make it into a batch yet, the view won't diff --git a/ReactAndroid/src/main/java/com/facebook/react/animated/PropsAnimatedNode.java b/ReactAndroid/src/main/java/com/facebook/react/animated/PropsAnimatedNode.java index 036f88435..de5c3e6f0 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/animated/PropsAnimatedNode.java +++ b/ReactAndroid/src/main/java/com/facebook/react/animated/PropsAnimatedNode.java @@ -9,7 +9,6 @@ package com.facebook.react.animated; -import com.facebook.react.bridge.JSApplicationIllegalArgumentException; import com.facebook.react.bridge.JavaOnlyMap; import com.facebook.react.bridge.ReadableMap; import com.facebook.react.bridge.ReadableMapKeySetIterator; @@ -28,78 +27,47 @@ import javax.annotation.Nullable; */ /*package*/ class PropsAnimatedNode extends AnimatedNode { - private int mConnectedViewTag = -1; - private final NativeAnimatedNodesManager mNativeAnimatedNodesManager; - private final UIImplementation mUIImplementation; - private final Map mPropNodeMapping; - // This is the backing map for `mDiffMap` we can mutate this to update it instead of having to - // create a new one for each update. - private final JavaOnlyMap mPropMap; - private final ReactStylesDiffMap mDiffMap; + /*package*/ int mConnectedViewTag = -1; - PropsAnimatedNode(ReadableMap config, NativeAnimatedNodesManager nativeAnimatedNodesManager, UIImplementation uiImplementation) { + private final NativeAnimatedNodesManager mNativeAnimatedNodesManager; + private final Map mPropMapping; + + PropsAnimatedNode(ReadableMap config, NativeAnimatedNodesManager nativeAnimatedNodesManager) { ReadableMap props = config.getMap("props"); ReadableMapKeySetIterator iter = props.keySetIterator(); - mPropNodeMapping = new HashMap<>(); + mPropMapping = new HashMap<>(); while (iter.hasNextKey()) { String propKey = iter.nextKey(); int nodeIndex = props.getInt(propKey); - mPropNodeMapping.put(propKey, nodeIndex); + mPropMapping.put(propKey, nodeIndex); } - mPropMap = new JavaOnlyMap(); - mDiffMap = new ReactStylesDiffMap(mPropMap); mNativeAnimatedNodesManager = nativeAnimatedNodesManager; - mUIImplementation = uiImplementation; } - public void connectToView(int viewTag) { - if (mConnectedViewTag != -1) { - throw new JSApplicationIllegalArgumentException("Animated node " + mTag + " is " + - "already attached to a view"); - } - mConnectedViewTag = viewTag; - } - - public void disconnectFromView(int viewTag) { - if (mConnectedViewTag != viewTag) { - throw new JSApplicationIllegalArgumentException("Attempting to disconnect view that has " + - "not been connected with the given animated node"); - } - - mConnectedViewTag = -1; - } - - public void restoreDefaultValues() { - ReadableMapKeySetIterator it = mPropMap.keySetIterator(); - while(it.hasNextKey()) { - mPropMap.putNull(it.nextKey()); - } - - mUIImplementation.synchronouslyUpdateViewOnUIThread( - mConnectedViewTag, - mDiffMap); - } - - public final void updateView() { + public final void updateView(UIImplementation uiImplementation) { if (mConnectedViewTag == -1) { - return; + throw new IllegalStateException("Node has not been attached to a view"); } - for (Map.Entry entry : mPropNodeMapping.entrySet()) { + JavaOnlyMap propsMap = new JavaOnlyMap(); + for (Map.Entry entry : mPropMapping.entrySet()) { @Nullable AnimatedNode node = mNativeAnimatedNodesManager.getNodeById(entry.getValue()); if (node == null) { throw new IllegalArgumentException("Mapped property node does not exists"); } else if (node instanceof StyleAnimatedNode) { - ((StyleAnimatedNode) node).collectViewUpdates(mPropMap); + ((StyleAnimatedNode) node).collectViewUpdates(propsMap); } else if (node instanceof ValueAnimatedNode) { - mPropMap.putDouble(entry.getKey(), ((ValueAnimatedNode) node).getValue()); + propsMap.putDouble(entry.getKey(), ((ValueAnimatedNode) node).getValue()); } else { throw new IllegalArgumentException("Unsupported type of node used in property node " + node.getClass()); } } - - mUIImplementation.synchronouslyUpdateViewOnUIThread( + // TODO: Reuse propsMap and stylesDiffMap objects - note that in subsequent animation steps + // for a given node most of the time we will be creating the same set of props (just with + // different values). We can take advantage on that and optimize the way we allocate property + // maps (we also know that updating view props doesn't retain a reference to the styles object). + uiImplementation.synchronouslyUpdateViewOnUIThread( mConnectedViewTag, - mDiffMap); + new ReactStylesDiffMap(propsMap)); } } diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIImplementation.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIImplementation.java index c5873d360..95ca85c9a 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIImplementation.java +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIImplementation.java @@ -881,10 +881,6 @@ public class UIImplementation { mOperationsQueue.enqueueUIBlock(block); } - public void prependUIBlock(UIBlock block) { - mOperationsQueue.prependUIBlock(block); - } - public int resolveRootTagFromReactTag(int reactTag) { if (mShadowNodeRegistry.isRootNode(reactTag)) { return reactTag; diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIManagerModule.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIManagerModule.java index e8bd2f101..d6b7d554b 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIManagerModule.java +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIManagerModule.java @@ -11,7 +11,6 @@ package com.facebook.react.uimanager; import javax.annotation.Nullable; -import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -86,7 +85,6 @@ public class UIManagerModule extends ReactContextBaseJavaModule implements private final Map mModuleConstants; private final UIImplementation mUIImplementation; private final MemoryTrimCallback mMemoryTrimCallback = new MemoryTrimCallback(); - private final List mListeners = new ArrayList<>(); private int mNextRootViewTag = 1; private int mBatchId = 0; @@ -535,9 +533,6 @@ public class UIManagerModule extends ReactContextBaseJavaModule implements SystraceMessage.beginSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, "onBatchCompleteUI") .arg("BatchId", batchId) .flush(); - for (UIManagerModuleListener listener : mListeners) { - listener.willDispatchViewUpdates(this); - } try { mUIImplementation.dispatchViewUpdates(batchId); } finally { @@ -575,28 +570,10 @@ public class UIManagerModule extends ReactContextBaseJavaModule implements } }); */ - public void addUIBlock(UIBlock block) { + public void addUIBlock (UIBlock block) { mUIImplementation.addUIBlock(block); } - /** - * Schedule a block to be executed on the UI thread. Useful if you need to execute - * view logic before all currently queued view updates have completed. - * - * @param block that contains UI logic you want to execute. - */ - public void prependUIBlock(UIBlock block) { - mUIImplementation.prependUIBlock(block); - } - - public void addUIManagerListener(UIManagerModuleListener listener) { - mListeners.add(listener); - } - - public void removeUIManagerListener(UIManagerModuleListener listener) { - mListeners.remove(listener); - } - /** * Given a reactTag from a component, find its root node tag, if possible. * Otherwise, this will return 0. If the reactTag belongs to a root node, this diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIManagerModuleListener.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIManagerModuleListener.java deleted file mode 100644 index 1dda859eb..000000000 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIManagerModuleListener.java +++ /dev/null @@ -1,21 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - */ - -package com.facebook.react.uimanager; - -/** - * Listener used to hook into the UIManager update process. - */ -public interface UIManagerModuleListener { - /** - * Called right before view updates are dispatched at the end of a batch. This is useful if a - * module needs to add UIBlocks to the queue before it is flushed. - */ - void willDispatchViewUpdates(UIManagerModule uiManager); -} diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIViewOperationQueue.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIViewOperationQueue.java index 5633e49dc..cf6ceac30 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIViewOperationQueue.java +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIViewOperationQueue.java @@ -725,10 +725,6 @@ public class UIViewOperationQueue { mOperations.add(new UIBlockOperation(block)); } - public void prependUIBlock(UIBlock block) { - mOperations.add(0, new UIBlockOperation(block)); - } - /* package */ void dispatchViewUpdates(final int batchId) { SystraceMessage.beginSection( Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, diff --git a/ReactAndroid/src/test/java/com/facebook/react/animated/BUCK b/ReactAndroid/src/test/java/com/facebook/react/animated/BUCK index 4a175cc5a..a81a1ab62 100644 --- a/ReactAndroid/src/test/java/com/facebook/react/animated/BUCK +++ b/ReactAndroid/src/test/java/com/facebook/react/animated/BUCK @@ -19,7 +19,6 @@ rn_robolectric_test( react_native_target("java/com/facebook/react/animated:animated"), react_native_target("java/com/facebook/react/bridge:bridge"), react_native_target("java/com/facebook/react/common:common"), - react_native_target("java/com/facebook/react/modules/core:core"), react_native_target("java/com/facebook/react/uimanager:uimanager"), react_native_tests_target("java/com/facebook/react/bridge:testhelpers"), ], diff --git a/ReactAndroid/src/test/java/com/facebook/react/animated/NativeAnimatedModuleTest.java b/ReactAndroid/src/test/java/com/facebook/react/animated/NativeAnimatedModuleTest.java deleted file mode 100644 index 8cd5dfb87..000000000 --- a/ReactAndroid/src/test/java/com/facebook/react/animated/NativeAnimatedModuleTest.java +++ /dev/null @@ -1,123 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - */ - - -package com.facebook.react.animated; - -import com.facebook.react.bridge.ReactApplicationContext; -import com.facebook.react.modules.core.ReactChoreographer; -import com.facebook.react.uimanager.NativeViewHierarchyManager; -import com.facebook.react.uimanager.UIBlock; -import com.facebook.react.uimanager.UIManagerModule; -import com.facebook.react.uimanager.events.EventDispatcher; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.InOrder; -import org.mockito.invocation.InvocationOnMock; -import org.mockito.stubbing.Answer; -import org.powermock.api.mockito.PowerMockito; -import org.powermock.core.classloader.annotations.PowerMockIgnore; -import org.powermock.core.classloader.annotations.PrepareEverythingForTest; -import org.robolectric.RobolectricTestRunner; - -import java.util.ArrayList; -import java.util.List; - -import static org.mockito.Matchers.any; -import static org.mockito.Matchers.anyInt; -import static org.mockito.Mockito.inOrder; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; - -@PrepareEverythingForTest -@RunWith(RobolectricTestRunner.class) -@PowerMockIgnore({"org.mockito.*", "org.robolectric.*", "android.*"}) -public class NativeAnimatedModuleTest { - private NativeAnimatedModule mNativeAnimatedModule; - private NativeAnimatedNodesManager mNodeManager; - private UIManagerModule mUIManager; - private List mUIBlocks; - - private void flushUIBlocks() { - mNativeAnimatedModule.willDispatchViewUpdates(mUIManager); - for (UIBlock block : mUIBlocks) { - block.execute(mock(NativeViewHierarchyManager.class)); - } - mUIBlocks.clear(); - } - - @Before - public void setUp() { - ReactApplicationContext context = mock(ReactApplicationContext.class); - mUIManager = mock(UIManagerModule.class); - PowerMockito.doReturn(mock(EventDispatcher.class)).when(mUIManager).getEventDispatcher(); - PowerMockito.doAnswer(new Answer() { - @Override - public Void answer(InvocationOnMock invocation) throws Throwable { - mUIBlocks.add(invocation.getArgumentAt(0, UIBlock.class)); - return null; - } - }).when(mUIManager).addUIBlock(any(UIBlock.class)); - PowerMockito.doAnswer(new Answer() { - @Override - public Void answer(InvocationOnMock invocation) throws Throwable { - mUIBlocks.add(0, invocation.getArgumentAt(0, UIBlock.class)); - return null; - } - }).when(mUIManager).prependUIBlock(any(UIBlock.class)); - PowerMockito.doReturn(mUIManager).when(context).getNativeModule(UIManagerModule.class); - - ReactChoreographer.initialize(); - mNativeAnimatedModule = new NativeAnimatedModule(context); - mNativeAnimatedModule.initialize(); - mNodeManager = mock(NativeAnimatedNodesManager.class); - mNativeAnimatedModule.setNodesManager(mNodeManager); - mUIBlocks = new ArrayList<>(); - } - - @Test - public void testNodeManagerSchedulingWaitsForFlush() { - // Make sure operations are not executed until UIManager has flushed it's queue. - mNativeAnimatedModule.connectAnimatedNodeToView(1, 1000); - verify(mNodeManager, never()).connectAnimatedNodeToView(anyInt(), anyInt()); - flushUIBlocks(); - verify(mNodeManager, times(1)).connectAnimatedNodeToView(anyInt(), anyInt()); - } - - @Test - public void testNodeManagerSchedulingOperation() { - UIBlock otherBlock = mock(UIBlock.class); - mUIManager.addUIBlock(otherBlock); - mNativeAnimatedModule.connectAnimatedNodeToView(1, 1000); - flushUIBlocks(); - - // Connect should be called after other UI operations. - InOrder inOrder = inOrder(mNodeManager, otherBlock); - inOrder.verify(otherBlock, times(1)).execute(any(NativeViewHierarchyManager.class)); - inOrder.verify(mNodeManager, times(1)).connectAnimatedNodeToView(1, 1000); - } - - @Test - public void testNodeManagerSchedulingPreOperation() { - UIBlock otherBlock = mock(UIBlock.class); - mUIManager.addUIBlock(otherBlock); - mNativeAnimatedModule.disconnectAnimatedNodeFromView(1, 1000); - flushUIBlocks(); - - // Disconnect should be called before other UI operations. - InOrder inOrder = inOrder(mNodeManager, otherBlock); - inOrder.verify(mNodeManager, times(1)).restoreDefaultValues(1, 1000); - inOrder.verify(otherBlock, times(1)).execute(any(NativeViewHierarchyManager.class)); - inOrder.verify(mNodeManager, times(1)).disconnectAnimatedNodeFromView(1, 1000); - } -} diff --git a/ReactAndroid/src/test/java/com/facebook/react/animated/NativeAnimatedNodeTraversalTest.java b/ReactAndroid/src/test/java/com/facebook/react/animated/NativeAnimatedNodeTraversalTest.java index 91dd104bc..ced79811c 100644 --- a/ReactAndroid/src/test/java/com/facebook/react/animated/NativeAnimatedNodeTraversalTest.java +++ b/ReactAndroid/src/test/java/com/facebook/react/animated/NativeAnimatedNodeTraversalTest.java @@ -992,51 +992,4 @@ public class NativeAnimatedNodeTraversalTest { verify(mUIImplementationMock).synchronouslyUpdateViewOnUIThread(eq(viewTag), stylesCaptor.capture()); assertThat(stylesCaptor.getValue().getDouble("opacity", Double.NaN)).isEqualTo(10); } - - @Test - public void testRestoreDefaultProps() { - int viewTag = 1000; - int propsNodeTag = 3; - mNativeAnimatedNodesManager.createAnimatedNode( - 1, - JavaOnlyMap.of("type", "value", "value", 1d, "offset", 0d)); - mNativeAnimatedNodesManager.createAnimatedNode( - 2, - JavaOnlyMap.of("type", "style", "style", JavaOnlyMap.of("opacity", 1))); - mNativeAnimatedNodesManager.createAnimatedNode( - propsNodeTag, - JavaOnlyMap.of("type", "props", "props", JavaOnlyMap.of("style", 2))); - mNativeAnimatedNodesManager.connectAnimatedNodes(1, 2); - mNativeAnimatedNodesManager.connectAnimatedNodes(2, propsNodeTag); - mNativeAnimatedNodesManager.connectAnimatedNodeToView(propsNodeTag, viewTag); - - JavaOnlyArray frames = JavaOnlyArray.of(0d, 0.5d, 1d); - Callback animationCallback = mock(Callback.class); - mNativeAnimatedNodesManager.startAnimatingNode( - 1, - 1, - JavaOnlyMap.of("type", "frames", "frames", frames, "toValue", 0d), - animationCallback); - - ArgumentCaptor stylesCaptor = - ArgumentCaptor.forClass(ReactStylesDiffMap.class); - - reset(mUIImplementationMock); - mNativeAnimatedNodesManager.runUpdates(nextFrameTime()); - verify(mUIImplementationMock).synchronouslyUpdateViewOnUIThread(eq(viewTag), stylesCaptor.capture()); - assertThat(stylesCaptor.getValue().getDouble("opacity", Double.NaN)).isEqualTo(1); - - for (int i = 0; i < frames.size(); i++) { - reset(mUIImplementationMock); - mNativeAnimatedNodesManager.runUpdates(nextFrameTime()); - } - - verify(mUIImplementationMock).synchronouslyUpdateViewOnUIThread(eq(viewTag), stylesCaptor.capture()); - assertThat(stylesCaptor.getValue().getDouble("opacity", Double.NaN)).isEqualTo(0); - - reset(mUIImplementationMock); - mNativeAnimatedNodesManager.restoreDefaultValues(propsNodeTag, viewTag); - verify(mUIImplementationMock).synchronouslyUpdateViewOnUIThread(eq(viewTag), stylesCaptor.capture()); - assertThat(stylesCaptor.getValue().isNull("opacity")); - } }