From 6aea98441a375dddc524f21abc53cafe9cfa5ebb Mon Sep 17 00:00:00 2001 From: David Vacca Date: Fri, 1 Jun 2018 17:47:40 -0700 Subject: [PATCH] Add backward compatible support for onLayout event in Fabric Reviewed By: achen1 Differential Revision: D8231722 fbshipit-source-id: 3d0641a7813e742ca81b98576f9ffc30ee597f30 --- .../react/testing/ReactAppTestActivity.java | 6 ++- .../testing/ReactInstrumentationTest.java | 5 ++- .../react/fabric/FabricUIManager.java | 24 ++++++++++- .../fabric/events/FabricEventEmitter.java | 11 +++-- .../uimanager/NativeViewHierarchyManager.java | 4 +- .../react/uimanager/UIViewOperationQueue.java | 42 +++++++++++++++++++ .../react/fabric/FabricReconcilerTest.java | 4 +- .../react/fabric/FabricUIManagerTest.java | 4 +- 8 files changed, 90 insertions(+), 10 deletions(-) diff --git a/ReactAndroid/src/androidTest/java/com/facebook/react/testing/ReactAppTestActivity.java b/ReactAndroid/src/androidTest/java/com/facebook/react/testing/ReactAppTestActivity.java index f01d74e1fe..77b2112177 100644 --- a/ReactAndroid/src/androidTest/java/com/facebook/react/testing/ReactAppTestActivity.java +++ b/ReactAndroid/src/androidTest/java/com/facebook/react/testing/ReactAppTestActivity.java @@ -37,7 +37,9 @@ import com.facebook.react.modules.core.PermissionListener; import com.facebook.react.shell.MainReactPackage; import com.facebook.react.testing.idledetection.ReactBridgeIdleSignaler; import com.facebook.react.testing.idledetection.ReactIdleDetectionUtil; +import com.facebook.react.uimanager.events.EventDispatcher; import com.facebook.react.uimanager.UIImplementationProvider; +import com.facebook.react.uimanager.UIManagerModule; import com.facebook.react.uimanager.ViewManager; import com.facebook.react.uimanager.ViewManagerRegistry; import java.util.Arrays; @@ -265,8 +267,10 @@ public class ReactAppTestActivity extends FragmentActivity public FabricUIManager get() { List viewManagers = mReactInstanceManager.getOrCreateViewManagers(reactApplicationContext); + EventDispatcher eventDispatcher = + reactApplicationContext.getNativeModule(UIManagerModule.class).getEventDispatcher(); FabricUIManager fabricUIManager = - new FabricUIManager(reactApplicationContext, new ViewManagerRegistry(viewManagers), jsContext); + new FabricUIManager(reactApplicationContext, new ViewManagerRegistry(viewManagers), jsContext, eventDispatcher); new FabricJSCBinding().installFabric(jsContext, fabricUIManager); return fabricUIManager; } diff --git a/ReactAndroid/src/androidTest/java/com/facebook/react/testing/ReactInstrumentationTest.java b/ReactAndroid/src/androidTest/java/com/facebook/react/testing/ReactInstrumentationTest.java index 20dcc3ef51..938835f3de 100644 --- a/ReactAndroid/src/androidTest/java/com/facebook/react/testing/ReactInstrumentationTest.java +++ b/ReactAndroid/src/androidTest/java/com/facebook/react/testing/ReactInstrumentationTest.java @@ -25,6 +25,8 @@ import com.facebook.react.testing.idledetection.IdleWaiter; public abstract class ReactInstrumentationTest extends ActivityInstrumentationTestCase2 implements IdleWaiter { + protected StringRecordingModule mRecordingModule; + public ReactInstrumentationTest() { super(ReactAppTestActivity.class); } @@ -36,6 +38,7 @@ public abstract class ReactInstrumentationTest extends Intent intent = new Intent(); intent.putExtra(ReactAppTestActivity.EXTRA_IS_FABRIC_TEST, isFabricTest()); setActivityIntent(intent); + mRecordingModule = new StringRecordingModule(); final ReactAppTestActivity activity = getActivity(); activity.loadBundle( createReactInstanceSpecForTest(), @@ -95,7 +98,7 @@ public abstract class ReactInstrumentationTest extends * Override this method to provide extra native modules to be loaded before the app starts */ protected ReactInstanceSpecForTest createReactInstanceSpecForTest() { - return new ReactInstanceSpecForTest(); + return new ReactInstanceSpecForTest().addNativeModule(mRecordingModule); } /** diff --git a/ReactAndroid/src/main/java/com/facebook/react/fabric/FabricUIManager.java b/ReactAndroid/src/main/java/com/facebook/react/fabric/FabricUIManager.java index d6d730bf8c..4442a8b153 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/fabric/FabricUIManager.java +++ b/ReactAndroid/src/main/java/com/facebook/react/fabric/FabricUIManager.java @@ -10,6 +10,7 @@ package com.facebook.react.fabric; import static android.view.View.MeasureSpec.AT_MOST; import static android.view.View.MeasureSpec.EXACTLY; import static android.view.View.MeasureSpec.UNSPECIFIED; +import static com.facebook.react.uimanager.common.UIManagerType.FABRIC; import android.util.Log; import android.view.View; @@ -24,9 +25,11 @@ import com.facebook.react.bridge.WritableMap; import com.facebook.react.bridge.WritableNativeMap; import com.facebook.react.common.ReactConstants; import com.facebook.react.common.annotations.VisibleForTesting; +import com.facebook.react.fabric.events.FabricEventEmitter; import com.facebook.react.modules.i18nmanager.I18nUtil; import com.facebook.react.uimanager.DisplayMetricsHolder; import com.facebook.react.uimanager.NativeViewHierarchyManager; +import com.facebook.react.uimanager.OnLayoutEvent; import com.facebook.react.uimanager.ReactRootViewTagGenerator; import com.facebook.react.uimanager.ReactShadowNode; import com.facebook.react.uimanager.ReactShadowNodeImpl; @@ -37,6 +40,7 @@ import com.facebook.react.uimanager.ViewManager; import com.facebook.react.uimanager.ViewManagerRegistry; import com.facebook.react.uimanager.common.MeasureSpecProvider; import com.facebook.react.uimanager.common.SizeMonitoringFrameLayout; +import com.facebook.react.uimanager.events.EventDispatcher; import com.facebook.yoga.YogaDirection; import java.util.ArrayList; import java.util.LinkedList; @@ -61,13 +65,15 @@ public class FabricUIManager implements UIManager, JSHandler { private final JavaScriptContextHolder mJSContext; private volatile int mCurrentBatch = 0; private final FabricReconciler mFabricReconciler; + private final EventDispatcher mEventDispatcher; private FabricBinding mBinding; private long mEventHandlerPointer; public FabricUIManager( ReactApplicationContext reactContext, ViewManagerRegistry viewManagerRegistry, - JavaScriptContextHolder jsContext) { + JavaScriptContextHolder jsContext, + EventDispatcher eventDispatcher) { DisplayMetricsHolder.initDisplayMetricsIfNotInitialized(reactContext); mReactApplicationContext = reactContext; mViewManagerRegistry = viewManagerRegistry; @@ -76,7 +82,12 @@ public class FabricUIManager implements UIManager, JSHandler { new UIViewOperationQueue( reactContext, mNativeViewHierarchyManager, 0); mFabricReconciler = new FabricReconciler(mUIViewOperationQueue); + mEventDispatcher = eventDispatcher; mJSContext = jsContext; + + FabricEventEmitter eventEmitter = + new FabricEventEmitter(reactContext, this); + eventDispatcher.registerEventEmitter(FABRIC, eventEmitter); } public void setBinding(FabricBinding binding) { @@ -355,7 +366,18 @@ public class FabricUIManager implements UIManager, JSHandler { if (mRootShadowNodeRegistry.getNode(tag) == null) { boolean frameDidChange = node.dispatchUpdates(absoluteX, absoluteY, mUIViewOperationQueue, null); + // Notify JS about layout event if requested + // and if the position or dimensions actually changed + // (consistent with iOS and Android Default implementation). + if (frameDidChange && node.shouldNotifyOnLayout()) { + mUIViewOperationQueue.enqueueOnLayoutEvent(tag, + node.getScreenX(), + node.getScreenY(), + node.getScreenWidth(), + node.getScreenHeight()); + } } + // Set the reference to the OriginalReactShadowNode to NULL, as the tree is already committed // and we do not need to hold references to the previous tree anymore node.setOriginalReactShadowNode(null); diff --git a/ReactAndroid/src/main/java/com/facebook/react/fabric/events/FabricEventEmitter.java b/ReactAndroid/src/main/java/com/facebook/react/fabric/events/FabricEventEmitter.java index 6ec640bf3c..0077b953dc 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/fabric/events/FabricEventEmitter.java +++ b/ReactAndroid/src/main/java/com/facebook/react/fabric/events/FabricEventEmitter.java @@ -26,6 +26,7 @@ import com.facebook.react.bridge.WritableNativeMap; import com.facebook.react.fabric.FabricUIManager; import com.facebook.react.fabric.Scheduler; import com.facebook.react.fabric.Work; +import com.facebook.react.uimanager.IllegalViewOperationException; import com.facebook.react.uimanager.events.RCTEventEmitter; import java.io.Closeable; import java.io.IOException; @@ -48,9 +49,13 @@ public class FabricEventEmitter implements RCTEventEmitter, Closeable { } @Override - public void receiveEvent(int targetTag, String eventName, @Nullable WritableMap params) { - long eventTarget = mFabricUIManager.createEventTarget(targetTag); - mScheduler.scheduleWork(new FabricUIManagerWork(eventTarget, eventName, params)); + public void receiveEvent(int reactTag, String eventName, @Nullable WritableMap params) { + try { + long eventTarget = mFabricUIManager.createEventTarget(reactTag); + mScheduler.scheduleWork(new FabricUIManagerWork(eventTarget, eventName, params)); + } catch (IllegalViewOperationException e) { + Log.e(TAG, "Unable to emmit event for tag " + reactTag, e); + } } @Override diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/NativeViewHierarchyManager.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/NativeViewHierarchyManager.java index 4cb9a92dc5..8fe0c5cdd3 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/NativeViewHierarchyManager.java +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/NativeViewHierarchyManager.java @@ -224,11 +224,11 @@ public class NativeViewHierarchyManager { public long getInstanceHandle(int reactTag) { View view = mTagsToViews.get(reactTag); if (view == null) { - throw new IllegalArgumentException("Unable to find view for tag: " + reactTag); + throw new IllegalViewOperationException("Unable to find view for tag: " + reactTag); } Long instanceHandle = (Long) view.getTag(R.id.view_tag_instance_handle); if (instanceHandle == null) { - throw new IllegalArgumentException("Unable to find instanceHandle for tag: " + reactTag); + throw new IllegalViewOperationException("Unable to find instanceHandle for tag: " + reactTag); } return instanceHandle; } 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 90e4d82bb9..783110b96a 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIViewOperationQueue.java +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIViewOperationQueue.java @@ -96,6 +96,38 @@ public class UIViewOperationQueue { } } + private final class EmitOnLayoutEventOperation extends ViewOperation { + + private final int mScreenX; + private final int mScreenY; + private final int mScreenWidth; + private final int mScreenHeight; + + public EmitOnLayoutEventOperation( + int tag, + int screenX, + int screenY, + int screenWidth, + int screenHeight) { + super(tag); + mScreenX = screenX; + mScreenY = screenY; + mScreenWidth = screenWidth; + mScreenHeight = screenHeight; + } + + @Override + public void execute() { + mReactApplicationContext.getNativeModule(UIManagerModule.class) + .getEventDispatcher() + .dispatchEvent(OnLayoutEvent.obtain( + mTag, + mScreenX, + mScreenY, + mScreenWidth, + mScreenHeight)); + } + } private final class UpdateInstanceHandleOperation extends ViewOperation { @@ -706,6 +738,16 @@ public class UIViewOperationQueue { mOperations.add(new UpdatePropertiesOperation(reactTag, props)); } + public void enqueueOnLayoutEvent( + int tag, + int screenX, + int screenY, + int screenWidth, + int screenHeight) { + mOperations.add(new EmitOnLayoutEventOperation(tag, screenX, screenY, screenWidth, screenHeight)); + } + + public void enqueueUpdateLayout( int parentTag, int reactTag, diff --git a/ReactAndroid/src/test/java/com/facebook/react/fabric/FabricReconcilerTest.java b/ReactAndroid/src/test/java/com/facebook/react/fabric/FabricReconcilerTest.java index a696d21e4d..b627df9af6 100644 --- a/ReactAndroid/src/test/java/com/facebook/react/fabric/FabricReconcilerTest.java +++ b/ReactAndroid/src/test/java/com/facebook/react/fabric/FabricReconcilerTest.java @@ -11,6 +11,7 @@ import com.facebook.react.bridge.CatalystInstance; import com.facebook.react.bridge.JavaScriptContextHolder; import com.facebook.react.bridge.ReactApplicationContext; import com.facebook.react.bridge.ReactTestHelper; +import com.facebook.react.uimanager.events.EventDispatcher; import com.facebook.react.uimanager.NativeViewHierarchyManager; import com.facebook.react.uimanager.ReactShadowNode; import com.facebook.react.uimanager.ReactShadowNodeImpl; @@ -46,7 +47,8 @@ public class FabricReconcilerTest { List viewManagers = new ArrayList<>(); ViewManagerRegistry viewManagerRegistry = new ViewManagerRegistry(viewManagers); JavaScriptContextHolder jsContext = mock(JavaScriptContextHolder.class); - mFabricUIManager = new FabricUIManager(reactContext, viewManagerRegistry, jsContext); + EventDispatcher eventDispatcher = mock(EventDispatcher.class); + mFabricUIManager = new FabricUIManager(reactContext, viewManagerRegistry, jsContext, eventDispatcher); mMockUIViewOperationQueue = new MockUIViewOperationQueue(reactContext); mFabricReconciler = new FabricReconciler(mMockUIViewOperationQueue); } diff --git a/ReactAndroid/src/test/java/com/facebook/react/fabric/FabricUIManagerTest.java b/ReactAndroid/src/test/java/com/facebook/react/fabric/FabricUIManagerTest.java index 9b1c95db67..ba6b10c8c3 100644 --- a/ReactAndroid/src/test/java/com/facebook/react/fabric/FabricUIManagerTest.java +++ b/ReactAndroid/src/test/java/com/facebook/react/fabric/FabricUIManagerTest.java @@ -13,6 +13,7 @@ import com.facebook.react.bridge.JavaScriptContextHolder; import com.facebook.react.bridge.ReactApplicationContext; import com.facebook.react.bridge.ReactTestHelper; import com.facebook.react.bridge.ReadableNativeMap; +import com.facebook.react.uimanager.events.EventDispatcher; import com.facebook.react.uimanager.ReactShadowNode; import com.facebook.react.uimanager.ReactShadowNodeImpl; import com.facebook.react.uimanager.Spacing; @@ -55,7 +56,8 @@ public class FabricUIManagerTest { new ReactViewManager(), new ReactTextViewManager(), new ReactRawTextManager()); ViewManagerRegistry viewManagerRegistry = new ViewManagerRegistry(viewManagers); JavaScriptContextHolder jsContext = mock(JavaScriptContextHolder.class); - mFabricUIManager = new FabricUIManager(reactContext, viewManagerRegistry, jsContext); + EventDispatcher eventDispatcher = mock(EventDispatcher.class); + mFabricUIManager = new FabricUIManager(reactContext, viewManagerRegistry, jsContext, eventDispatcher); } @Test