From 51278d3f30f937ab201ddc3ec0bccfc18b4aed66 Mon Sep 17 00:00:00 2001 From: Ruslan Shestopalyuk Date: Fri, 2 Aug 2024 07:10:36 -0700 Subject: [PATCH] Migrate UIManager interface to Kotlin (#44589) Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/44589 ## Changelog: [Internal] - As in the title. Reviewed By: tdn120 Differential Revision: D57434432 fbshipit-source-id: ce2504d5a27a9e8dd8d8d02b052b6cf9414491ab --- .../com/facebook/react/bridge/UIManager.java | 162 ------------------ .../com/facebook/react/bridge/UIManager.kt | 160 +++++++++++++++++ .../java/com/facebook/react/RootViewTest.kt | 2 +- .../NativeAnimatedNodeTraversalTest.kt | 2 +- .../facebook/testutils/fakes/FakeUIManager.kt | 18 +- 5 files changed, 170 insertions(+), 174 deletions(-) delete mode 100644 packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/UIManager.java create mode 100644 packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/UIManager.kt diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/UIManager.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/UIManager.java deleted file mode 100644 index 9a9724b12cd..00000000000 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/UIManager.java +++ /dev/null @@ -1,162 +0,0 @@ -/* - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -package com.facebook.react.bridge; - -import static com.facebook.infer.annotation.ThreadConfined.UI; - -import android.view.View; -import androidx.annotation.AnyThread; -import androidx.annotation.Nullable; -import androidx.annotation.UiThread; -import com.facebook.infer.annotation.ThreadConfined; - -public interface UIManager extends PerformanceCounter { - - /** Registers a new root view. @Deprecated call startSurface instead */ - @UiThread - @ThreadConfined(UI) - @Deprecated - int addRootView(final T rootView, WritableMap initialProps); - - /** Registers a new root view with width and height. */ - @AnyThread - int startSurface( - final T rootView, - final String moduleName, - final WritableMap initialProps, - int widthMeasureSpec, - int heightMeasureSpec); - - /** - * Stop a surface from running in JS and clears up native memory usage. Assumes that the native - * View hierarchy has already been cleaned up. Fabric-only. - */ - @AnyThread - void stopSurface(final int surfaceId); - - /** - * Updates the layout specs of the RootShadowNode based on the Measure specs received by - * parameters. offsetX and offsetY are the position of the RootView within the screen. - */ - @UiThread - @ThreadConfined(UI) - void updateRootLayoutSpecs( - int rootTag, int widthMeasureSpec, int heightMeasureSpec, int offsetX, int offsetY); - - /** - * Dispatches the commandId received by parameter to the view associated with the reactTag. The - * command will be processed in the UIThread. - * - *

Receiving commands as ints is deprecated and will be removed in a future release. - * - *

Pre-Fabric, this is only called on the Native Module Thread. - * - * @param reactTag {@link int} that identifies the view that will receive this command - * @param commandId {@link int} command id - * @param commandArgs {@link ReadableArray} parameters associated with the command - */ - void dispatchCommand(int reactTag, int commandId, @Nullable ReadableArray commandArgs); - - /** - * Dispatches the commandId received by parameter to the view associated with the reactTag. The - * command will be processed in the UIThread. - * - *

Pre-Fabric, this is only called on the Native Module Thread. - * - * @param reactTag {@link int} that identifies the view that will receive this command - * @param commandId {@link String} command id - * @param commandArgs {@link ReadableArray} parameters associated with the command - */ - void dispatchCommand(int reactTag, String commandId, @Nullable ReadableArray commandArgs); - - /** - * @return the {@link EventDispatcher} object that is used by this class. - */ - T getEventDispatcher(); - - /** - * Used by native animated module to bypass the process of updating the values through the shadow - * view hierarchy. This method will directly update native views, which means that updates for - * layout-related propertied won't be handled properly. Make sure you know what you're doing - * before calling this method :) - * - * @param reactTag {@link int} that identifies the view that will be updated - * @param props {@link ReadableMap} props that should be immediately updated in view - */ - @UiThread - @ThreadConfined(UI) - void synchronouslyUpdateViewOnUIThread(int reactTag, ReadableMap props); - - /** - * Dispatch an accessibility event to a view asynchronously. - * - *

Pre-Fabric, this is only called on the Native Module Thread. - * - * @param reactTag - * @param eventType - */ - void sendAccessibilityEvent(int reactTag, int eventType); - - /** - * Register a {@link UIManagerListener} with this UIManager to receive lifecycle callbacks. - * - * @param listener - */ - void addUIManagerEventListener(UIManagerListener listener); - - /** - * Unregister a {@link UIManagerListener} from this UIManager to stop receiving lifecycle - * callbacks. - * - * @param listener - */ - void removeUIManagerEventListener(UIManagerListener listener); - - /** - * Resolves a view based on its reactTag. Do not mutate properties on this view that are already - * managed by React, as there are no guarantees this changes will be preserved. - * - * @throws IllegalViewOperationException if tag could not be resolved. - * @param reactTag tag - * @return view if found - */ - View resolveView(int reactTag); - - /** - * This method dispatches events from RN Android code to JS. The delivery of this event will not - * be queued in EventDispatcher class. - * - * @param reactTag tag - * @param eventName name of the event - * @param event parameters - */ - @Deprecated - void receiveEvent(int reactTag, String eventName, @Nullable WritableMap event); - - /** - * This method dispatches events from RN Android code to JS. The delivery of this event will not - * be queued in EventDispatcher class. - * - * @param surfaceId - * @param reactTag tag - * @param eventName name of the event - * @param event parameters - */ - void receiveEvent(int surfaceId, int reactTag, String eventName, @Nullable WritableMap event); - - /** Resolves Direct Event name exposed to JS from the one known to the Native side. */ - @Deprecated - @Nullable - String resolveCustomDirectEventName(@Nullable String eventName); - - /** This method is called after {@link ReactApplicationContext} has been created. */ - void initialize(); - - /** Called before React Native instance is destroyed. */ - void invalidate(); -} diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/UIManager.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/UIManager.kt new file mode 100644 index 00000000000..b12de973790 --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/UIManager.kt @@ -0,0 +1,160 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +package com.facebook.react.bridge + +import android.view.View +import androidx.annotation.AnyThread +import androidx.annotation.UiThread +import com.facebook.infer.annotation.ThreadConfined +import com.facebook.react.common.annotations.UnstableReactNativeAPI + +@OptIn(UnstableReactNativeAPI::class) +public interface UIManager : PerformanceCounter { + /** Registers a new root view. @Deprecated call startSurface instead */ + @UiThread + @ThreadConfined(ThreadConfined.UI) + @Deprecated("") + public fun addRootView(rootView: T, initialProps: WritableMap?): Int + + /** Registers a new root view with width and height. */ + @AnyThread + public fun startSurface( + rootView: T, + moduleName: String, + initialProps: WritableMap?, + widthMeasureSpec: Int, + heightMeasureSpec: Int + ): Int + + /** + * Stop a surface from running in JS and clears up native memory usage. Assumes that the native + * View hierarchy has already been cleaned up. Fabric-only. + */ + @AnyThread public fun stopSurface(surfaceId: Int) + + /** + * Updates the layout specs of the RootShadowNode based on the Measure specs received by + * parameters. offsetX and offsetY are the position of the RootView within the screen. + */ + @UiThread + @ThreadConfined(ThreadConfined.UI) + public fun updateRootLayoutSpecs( + rootTag: Int, + widthMeasureSpec: Int, + heightMeasureSpec: Int, + offsetX: Int, + offsetY: Int + ) + + /** + * Dispatches the commandId received by parameter to the view associated with the reactTag. The + * command will be processed in the UIThread. + * + * Receiving commands as ints is deprecated and will be removed in a future release. + * + * Pre-Fabric, this is only called on the Native Module Thread. + * + * @param reactTag that identifies the view that will receive this command + * @param commandId command id + * @param commandArgs [ReadableArray] parameters associated with the command + */ + public fun dispatchCommand(reactTag: Int, commandId: Int, commandArgs: ReadableArray?) + + /** + * Dispatches the commandId received by parameter to the view associated with the reactTag. The + * command will be processed in the UIThread. + * + * Pre-Fabric, this is only called on the Native Module Thread. + * + * @param reactTag that identifies the view that will receive this command + * @param commandId command id + * @param commandArgs [ReadableArray] parameters associated with the command + */ + public fun dispatchCommand(reactTag: Int, commandId: String, commandArgs: ReadableArray?) + + /** @return the [EventDispatcher] object that is used by this class. */ + public fun getEventDispatcher(): T + + /** + * Used by native animated module to bypass the process of updating the values through the shadow + * view hierarchy. This method will directly update native views, which means that updates for + * layout-related propertied won't be handled properly. Make sure you know what you're doing + * before calling this method :) + * + * @param reactTag that identifies the view that will be updated + * @param props [ReadableMap] props that should be immediately updated in view + */ + @UiThread + @ThreadConfined(ThreadConfined.UI) + public fun synchronouslyUpdateViewOnUIThread(reactTag: Int, props: ReadableMap?) + + /** + * Dispatch an accessibility event to a view asynchronously. + * + * Pre-Fabric, this is only called on the Native Module Thread. + * + * @param reactTag + * @param eventType + */ + public fun sendAccessibilityEvent(reactTag: Int, eventType: Int) + + /** + * Register a [UIManagerListener] with this UIManager to receive lifecycle callbacks. + * + * @param listener + */ + public fun addUIManagerEventListener(listener: UIManagerListener?) + + /** + * Unregister a [UIManagerListener] from this UIManager to stop receiving lifecycle callbacks. + * + * @param listener + */ + public fun removeUIManagerEventListener(listener: UIManagerListener?) + + /** + * Resolves a view based on its reactTag. Do not mutate properties on this view that are already + * managed by React, as there are no guarantees this changes will be preserved. + * + * @param reactTag tag + * @return view if found + * @throws IllegalViewOperationException if tag could not be resolved. + */ + public fun resolveView(reactTag: Int): View? + + /** + * This method dispatches events from RN Android code to JS. The delivery of this event will not + * be queued in EventDispatcher class. + * + * @param reactTag tag + * @param eventName name of the event + * @param event parameters + */ + @Deprecated("", ReplaceWith("receiveEvent(surfaceId, reactTag, eventName, event)")) + public fun receiveEvent(reactTag: Int, eventName: String, event: WritableMap?) + + /** + * This method dispatches events from RN Android code to JS. The delivery of this event will not + * be queued in EventDispatcher class. + * + * @param surfaceId + * @param reactTag tag + * @param eventName name of the event + * @param event parameters + */ + public fun receiveEvent(surfaceId: Int, reactTag: Int, eventName: String, event: WritableMap?) + + /** Resolves Direct Event name exposed to JS from the one known to the Native side. */ + @Deprecated("") public fun resolveCustomDirectEventName(eventName: String): String? + + /** This method is called after [ReactApplicationContext] has been created. */ + public fun initialize() + + /** Called before React Native instance is destroyed. */ + public fun invalidate() +} diff --git a/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/RootViewTest.kt b/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/RootViewTest.kt index 4285a84fb12..c68e1bbedb1 100644 --- a/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/RootViewTest.kt +++ b/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/RootViewTest.kt @@ -92,7 +92,7 @@ class RootViewTest { val eventEmitterModuleMock = mock(RCTEventEmitter::class.java) whenever(catalystInstanceMock.getNativeModule(UIManagerModule::class.java)) .thenReturn(uiManager) - whenever(uiManager.eventDispatcher).thenReturn(eventDispatcher) + whenever(uiManager.getEventDispatcher()).thenReturn(eventDispatcher) // RootView IDs is React Native follow the 11, 21, 31, ... progression. val rootViewId = 11 diff --git a/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/animated/NativeAnimatedNodeTraversalTest.kt b/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/animated/NativeAnimatedNodeTraversalTest.kt index f7b2cbb0272..9aeef500564 100644 --- a/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/animated/NativeAnimatedNodeTraversalTest.kt +++ b/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/animated/NativeAnimatedNodeTraversalTest.kt @@ -87,7 +87,7 @@ class NativeAnimatedNodeTraversalTest { uiManagerMock = mock(UIManagerModule::class.java) eventDispatcherMock = mock(EventDispatcher::class.java) - whenever(uiManagerMock.eventDispatcher).thenAnswer { eventDispatcherMock } + whenever(uiManagerMock.getEventDispatcher()).thenAnswer { eventDispatcherMock } whenever(uiManagerMock.constants).thenAnswer { MapBuilder.of("customDirectEventTypes", MapBuilder.newHashMap()) } diff --git a/packages/react-native/ReactAndroid/src/test/java/com/facebook/testutils/fakes/FakeUIManager.kt b/packages/react-native/ReactAndroid/src/test/java/com/facebook/testutils/fakes/FakeUIManager.kt index 439c0a61947..c51c0148329 100644 --- a/packages/react-native/ReactAndroid/src/test/java/com/facebook/testutils/fakes/FakeUIManager.kt +++ b/packages/react-native/ReactAndroid/src/test/java/com/facebook/testutils/fakes/FakeUIManager.kt @@ -28,13 +28,14 @@ class FakeUIManager : UIManager, UIBlockViewResolver { TODO("Not yet implemented") } + @Deprecated("") override fun addRootView(rootView: T, initialProps: WritableMap?): Int { TODO("Not yet implemented") } override fun startSurface( rootView: T, - moduleName: String?, + moduleName: String, initialProps: WritableMap?, widthMeasureSpec: Int, heightMeasureSpec: Int @@ -60,7 +61,7 @@ class FakeUIManager : UIManager, UIBlockViewResolver { TODO("Not yet implemented") } - override fun dispatchCommand(reactTag: Int, commandId: String?, commandArgs: ReadableArray?) { + override fun dispatchCommand(reactTag: Int, commandId: String, commandArgs: ReadableArray?) { TODO("Not yet implemented") } @@ -89,20 +90,17 @@ class FakeUIManager : UIManager, UIBlockViewResolver { return null } - override fun receiveEvent(reactTag: Int, eventName: String?, event: WritableMap?) { + @Deprecated("") + override fun receiveEvent(reactTag: Int, eventName: String, event: WritableMap?) { TODO("Not yet implemented") } - override fun receiveEvent( - surfaceId: Int, - reactTag: Int, - eventName: String?, - event: WritableMap? - ) { + override fun receiveEvent(surfaceId: Int, reactTag: Int, eventName: String, event: WritableMap?) { TODO("Not yet implemented") } - override fun resolveCustomDirectEventName(eventName: String?): String? { + @Deprecated("") + override fun resolveCustomDirectEventName(eventName: String): String? { TODO("Not yet implemented") }