From 6d7751ac554d550ea4347455ca715a34d2118ea3 Mon Sep 17 00:00:00 2001 From: Ruslan Shestopalyuk Date: Fri, 3 Jan 2025 23:44:10 -0800 Subject: [PATCH 1/5] Convert RCTModernEventEmitter Differential Revision: D67793108 --- .../react/animated/EventAnimationDriver.kt | 16 ++++---- .../react/fabric/events/FabricEventEmitter.kt | 12 +++--- .../events/RCTModernEventEmitter.java | 39 ------------------- .../uimanager/events/RCTModernEventEmitter.kt | 37 ++++++++++++++++++ .../uimanager/events/ReactEventEmitter.kt | 24 +++++------- 5 files changed, 60 insertions(+), 68 deletions(-) delete mode 100644 packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/events/RCTModernEventEmitter.java create mode 100644 packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/events/RCTModernEventEmitter.kt diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/animated/EventAnimationDriver.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/animated/EventAnimationDriver.kt index cf7600f1885e..f59b730cf225 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/animated/EventAnimationDriver.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/animated/EventAnimationDriver.kt @@ -25,17 +25,17 @@ internal class EventAnimationDriver( @JvmField internal var valueNode: ValueAnimatedNode ) : RCTModernEventEmitter { @Deprecated("Deprecated in Java") - override fun receiveEvent(targetReactTag: Int, eventName: String, event: WritableMap?) = - receiveEvent(-1, targetReactTag, eventName, event) + override fun receiveEvent(targetTag: Int, eventName: String, params: WritableMap?) = + receiveEvent(-1, targetTag, eventName, params) override fun receiveEvent( surfaceId: Int, targetTag: Int, eventName: String, - event: WritableMap? + params: WritableMap? ) = // We assume this event can't be coalesced. `customCoalesceKey` has no meaning in Fabric. - receiveEvent(surfaceId, targetTag, eventName, false, 0, event, EventCategoryDef.UNSPECIFIED) + receiveEvent(surfaceId, targetTag, eventName, false, 0, params, EventCategoryDef.UNSPECIFIED) @Deprecated("Deprecated in Java") override fun receiveTouches( @@ -47,7 +47,7 @@ internal class EventAnimationDriver( } @Deprecated("Deprecated in Java") - override fun receiveTouches(touchEvent: TouchEvent) { + override fun receiveTouches(event: TouchEvent) { throw UnsupportedOperationException("receiveTouches is not support by native animated events") } @@ -57,13 +57,13 @@ internal class EventAnimationDriver( eventName: String, canCoalesceEvent: Boolean, customCoalesceKey: Int, - event: WritableMap?, + params: WritableMap?, @EventCategoryDef category: Int ) { - requireNotNull(event) { "Native animated events must have event data." } + requireNotNull(params) { "Native animated events must have event data." } // Get the new value for the node by looking into the event map using the provided event path. - var currMap: ReadableMap? = event + var currMap: ReadableMap? = params var currArray: ReadableArray? = null for (i in 0 until eventPath.size - 1) { if (currMap != null) { diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/fabric/events/FabricEventEmitter.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/fabric/events/FabricEventEmitter.kt index 3420e0ed61b0..5395eca470a2 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/fabric/events/FabricEventEmitter.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/fabric/events/FabricEventEmitter.kt @@ -18,22 +18,22 @@ import com.facebook.systrace.Systrace public class FabricEventEmitter(private val uiManager: FabricUIManager) : RCTModernEventEmitter { @Deprecated("Deprecated in Java") - public override fun receiveEvent(reactTag: Int, eventName: String, params: WritableMap?): Unit { - receiveEvent(ViewUtil.NO_SURFACE_ID, reactTag, eventName, params) + public override fun receiveEvent(targetTag: Int, eventName: String, params: WritableMap?): Unit { + receiveEvent(ViewUtil.NO_SURFACE_ID, targetTag, eventName, params) } public override fun receiveEvent( surfaceId: Int, - reactTag: Int, + targetTag: Int, eventName: String, params: WritableMap? ) { - receiveEvent(surfaceId, reactTag, eventName, false, 0, params, EventCategoryDef.UNSPECIFIED) + receiveEvent(surfaceId, targetTag, eventName, false, 0, params, EventCategoryDef.UNSPECIFIED) } public override fun receiveEvent( surfaceId: Int, - reactTag: Int, + targetTag: Int, eventName: String, canCoalesceEvent: Boolean, customCoalesceKey: Int, @@ -43,7 +43,7 @@ public class FabricEventEmitter(private val uiManager: FabricUIManager) : RCTMod Systrace.beginSection( Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, "FabricEventEmitter.receiveEvent('$eventName')") try { - uiManager.receiveEvent(surfaceId, reactTag, eventName, canCoalesceEvent, params, category) + uiManager.receiveEvent(surfaceId, targetTag, eventName, canCoalesceEvent, params, category) } finally { Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE) } diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/events/RCTModernEventEmitter.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/events/RCTModernEventEmitter.java deleted file mode 100644 index 056c7dcdf3e2..000000000000 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/events/RCTModernEventEmitter.java +++ /dev/null @@ -1,39 +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.uimanager.events; - -import androidx.annotation.Nullable; -import com.facebook.react.bridge.WritableMap; - -/** - * This is a transitional replacement for RCTEventEmitter that works with Fabric and non-Fabric - * renderers. RCTEventEmitter works with Fabric as well, but there are negative perf implications - * and it should be avoided. - * - *

This interface will *also* be deleted in the distant future and be replaced with a new - * interface that doesn't need the old `receiveEvent` method at all. But for the foreseeable future, - * this is the recommended interface to use for EventEmitters. - */ -public interface RCTModernEventEmitter extends RCTEventEmitter { - void receiveEvent(int surfaceId, int targetTag, String eventName, @Nullable WritableMap event); - - void receiveEvent( - int surfaceId, - int targetTag, - String eventName, - boolean canCoalesceEvent, - int customCoalesceKey, - @Nullable WritableMap event, - @EventCategoryDef int category); - - /** - * @deprecated Dispatch the TouchEvent using {@link EventDispatcher} instead - */ - @Deprecated - void receiveTouches(TouchEvent event); -} diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/events/RCTModernEventEmitter.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/events/RCTModernEventEmitter.kt new file mode 100644 index 000000000000..dab9e0520172 --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/events/RCTModernEventEmitter.kt @@ -0,0 +1,37 @@ +/* + * 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.uimanager.events + +import com.facebook.react.bridge.WritableMap + +/** + * This is a transitional replacement for [RCTEventEmitter] that works with Fabric and non-Fabric + * renderers. [RCTEventEmitter] works with Fabric as well, but there are negative perf implications + * and it should be avoided. + * + * This interface will *also* be deleted in the distant future and be replaced with a new interface + * that doesn't need the old `receiveEvent` method at all. But for the foreseeable future, this is + * the recommended interface to use for EventEmitters. + */ +@Suppress("DEPRECATION") +public interface RCTModernEventEmitter : RCTEventEmitter { + public fun receiveEvent(surfaceId: Int, targetTag: Int, eventName: String, params: WritableMap?) + + public fun receiveEvent( + surfaceId: Int, + targetTag: Int, + eventName: String, + canCoalesceEvent: Boolean, + customCoalesceKey: Int, + params: WritableMap?, + @EventCategoryDef category: Int + ) + + @Deprecated("Dispatch the TouchEvent using [EventDispatcher] instead") + public fun receiveTouches(event: TouchEvent) +} diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/events/ReactEventEmitter.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/events/ReactEventEmitter.kt index 53ad8d04d3d7..07afeaf366e0 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/events/ReactEventEmitter.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/events/ReactEventEmitter.kt @@ -45,18 +45,18 @@ internal class ReactEventEmitter(private val reactContext: ReactApplicationConte } @Deprecated("Please use RCTModernEventEmitter") - override fun receiveEvent(targetReactTag: Int, eventName: String, event: WritableMap?) { - receiveEvent(-1, targetReactTag, eventName, event) + override fun receiveEvent(targetReactTag: Int, eventName: String, params: WritableMap?) { + receiveEvent(-1, targetReactTag, eventName, params) } override fun receiveEvent( surfaceId: Int, targetTag: Int, eventName: String, - event: WritableMap? + params: WritableMap? ) { // We assume this event can't be coalesced. `customCoalesceKey` has no meaning in Fabric. - receiveEvent(surfaceId, targetTag, eventName, false, 0, event, EventCategoryDef.UNSPECIFIED) + receiveEvent(surfaceId, targetTag, eventName, false, 0, params, EventCategoryDef.UNSPECIFIED) } @Deprecated("Please use RCTModernEventEmitter") @@ -108,25 +108,19 @@ internal class ReactEventEmitter(private val reactContext: ReactApplicationConte override fun receiveEvent( surfaceId: Int, - targetReactTag: Int, + targetTag: Int, eventName: String, canCoalesceEvent: Boolean, customCoalesceKey: Int, - event: WritableMap?, + params: WritableMap?, @EventCategoryDef category: Int ) { - @UIManagerType val uiManagerType = getUIManagerType(targetReactTag, surfaceId) + @UIManagerType val uiManagerType = getUIManagerType(targetTag, surfaceId) if (uiManagerType == UIManagerType.FABRIC) { fabricEventEmitter?.receiveEvent( - surfaceId, - targetReactTag, - eventName, - canCoalesceEvent, - customCoalesceKey, - event, - category) + surfaceId, targetTag, eventName, canCoalesceEvent, customCoalesceKey, params, category) } else if (uiManagerType == UIManagerType.DEFAULT) { - ensureDefaultEventEmitter()?.receiveEvent(targetReactTag, eventName, event) + ensureDefaultEventEmitter()?.receiveEvent(targetTag, eventName, params) } } From 87b53c7ff5ad47c0c10c81e6107c9c7c87cf0b19 Mon Sep 17 00:00:00 2001 From: Ruslan Shestopalyuk Date: Sat, 4 Jan 2025 03:38:16 -0800 Subject: [PATCH 2/5] Use proper mockito/kotlin for RootViewTest and JSPointerDispatcherTest Differential Revision: D67824679 --- .../java/com/facebook/react/RootViewTest.kt | 75 +++++++++++-------- .../uimanager/JSPointerDispatcherTest.kt | 8 +- 2 files changed, 46 insertions(+), 37 deletions(-) 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 4285a84fb127..6fb1d2a21c4a 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 @@ -27,21 +27,28 @@ import com.facebook.react.common.SystemClock import com.facebook.react.internal.featureflags.ReactNativeFeatureFlagsForTests import com.facebook.react.uimanager.DisplayMetricsHolder import com.facebook.react.uimanager.UIManagerModule -import com.facebook.react.uimanager.events.Event import com.facebook.react.uimanager.events.EventDispatcher import com.facebook.react.uimanager.events.RCTEventEmitter +import com.facebook.react.uimanager.events.TouchEvent import java.util.Date import org.assertj.core.api.Assertions.* import org.junit.After import org.junit.Before import org.junit.Test import org.junit.runner.RunWith -import org.mockito.ArgumentCaptor -import org.mockito.ArgumentMatchers import org.mockito.MockedStatic -import org.mockito.Mockito -import org.mockito.Mockito.* -import org.mockito.Mockito.`when` as whenever +import org.mockito.Mockito.mockStatic +import org.mockito.kotlin.KArgumentCaptor +import org.mockito.kotlin.any +import org.mockito.kotlin.argumentCaptor +import org.mockito.kotlin.eq +import org.mockito.kotlin.mock +import org.mockito.kotlin.reset +import org.mockito.kotlin.spy +import org.mockito.kotlin.times +import org.mockito.kotlin.verify +import org.mockito.kotlin.verifyNoMoreInteractions +import org.mockito.kotlin.whenever import org.robolectric.Robolectric import org.robolectric.RobolectricTestRunner import org.robolectric.RuntimeEnvironment @@ -55,16 +62,22 @@ class RootViewTest { private lateinit var arguments: MockedStatic private lateinit var systemClock: MockedStatic + private lateinit var downEventCaptor: KArgumentCaptor + private lateinit var downActionTouchesArgCaptor: KArgumentCaptor + + private lateinit var upEventCaptor: KArgumentCaptor + private lateinit var upActionTouchesArgCaptor: KArgumentCaptor + @Before fun setUp() { ReactNativeFeatureFlagsForTests.setUp() - arguments = Mockito.mockStatic(Arguments::class.java) + arguments = mockStatic(Arguments::class.java) arguments.`when` { Arguments.createArray() }.thenAnswer { JavaOnlyArray() } arguments.`when` { Arguments.createMap() }.thenAnswer { JavaOnlyMap() } val ts = SystemClock.uptimeMillis() - systemClock = Mockito.mockStatic(SystemClock::class.java) + systemClock = mockStatic(SystemClock::class.java) systemClock.`when` { SystemClock.uptimeMillis() }.thenReturn(ts) catalystInstanceMock = ReactTestHelper.createMockCatalystInstance() @@ -72,9 +85,15 @@ class RootViewTest { reactContext.initializeWithInstance(catalystInstanceMock) DisplayMetricsHolder.initDisplayMetricsIfNotInitialized(reactContext) - val uiManagerModuleMock = mock(UIManagerModule::class.java) + val uiManagerModuleMock: UIManagerModule = mock() whenever(catalystInstanceMock.getNativeModule(UIManagerModule::class.java)) .thenReturn(uiManagerModuleMock) + + downEventCaptor = argumentCaptor() + downActionTouchesArgCaptor = argumentCaptor() + + upEventCaptor = argumentCaptor() + upActionTouchesArgCaptor = argumentCaptor() } @After @@ -85,11 +104,11 @@ class RootViewTest { @Test fun testTouchEmitter() { - val instanceManager = mock(ReactInstanceManager::class.java) + val instanceManager: ReactInstanceManager = mock() whenever(instanceManager.currentReactContext).thenReturn(reactContext) - val uiManager = mock(UIManagerModule::class.java) - val eventDispatcher = mock(EventDispatcher::class.java) - val eventEmitterModuleMock = mock(RCTEventEmitter::class.java) + val uiManager: UIManagerModule = mock() + val eventDispatcher: EventDispatcher = mock() + val eventEmitterModuleMock: RCTEventEmitter = mock() whenever(catalystInstanceMock.getNativeModule(UIManagerModule::class.java)) .thenReturn(uiManager) whenever(uiManager.eventDispatcher).thenReturn(eventDispatcher) @@ -106,19 +125,14 @@ class RootViewTest { // Test ACTION_DOWN event rootView.onTouchEvent(MotionEvent.obtain(100, ts, MotionEvent.ACTION_DOWN, 0f, 0f, 0)) - val downEventCaptor = ArgumentCaptor.forClass(Event::class.java) verify(eventDispatcher).dispatchEvent(downEventCaptor.capture()) verifyNoMoreInteractions(eventDispatcher) - downEventCaptor.value.dispatch(eventEmitterModuleMock) - val downActionTouchesArgCaptor = ArgumentCaptor.forClass(JavaOnlyArray::class.java) + downEventCaptor.firstValue.dispatch(eventEmitterModuleMock) verify(eventEmitterModuleMock) - .receiveTouches( - ArgumentMatchers.eq("topTouchStart"), - downActionTouchesArgCaptor.capture(), - ArgumentMatchers.any(JavaOnlyArray::class.java)) + .receiveTouches(eq("topTouchStart"), downActionTouchesArgCaptor.capture(), any()) verifyNoMoreInteractions(eventEmitterModuleMock) - assertThat(downActionTouchesArgCaptor.value.size()).isEqualTo(1) - assertThat(downActionTouchesArgCaptor.value.getMap(0)) + assertThat(downActionTouchesArgCaptor.firstValue.size()).isEqualTo(1) + assertThat(downActionTouchesArgCaptor.firstValue.getMap(0)) .isEqualTo( JavaOnlyMap.of( "pageX", @@ -140,22 +154,17 @@ class RootViewTest { // Test ACTION_UP event reset(eventEmitterModuleMock, eventDispatcher) - val upEventCaptor = ArgumentCaptor.forClass(Event::class.java) - val upActionTouchesArgCaptor = ArgumentCaptor.forClass(JavaOnlyArray::class.java) rootView.onTouchEvent(MotionEvent.obtain(50, ts, MotionEvent.ACTION_UP, 0f, 0f, 0)) verify(eventDispatcher).dispatchEvent(upEventCaptor.capture()) verifyNoMoreInteractions(eventDispatcher) - upEventCaptor.value.dispatch(eventEmitterModuleMock) + upEventCaptor.firstValue.dispatch(eventEmitterModuleMock) verify(eventEmitterModuleMock) - .receiveTouches( - ArgumentMatchers.eq("topTouchEnd"), - upActionTouchesArgCaptor.capture(), - ArgumentMatchers.any(WritableArray::class.java)) + .receiveTouches(eq("topTouchEnd"), upActionTouchesArgCaptor.capture(), any()) verifyNoMoreInteractions(eventEmitterModuleMock) - assertThat(upActionTouchesArgCaptor.value.size()).isEqualTo(1) - assertThat(upActionTouchesArgCaptor.value.getMap(0)) + assertThat(upActionTouchesArgCaptor.firstValue.size()).isEqualTo(1) + assertThat(upActionTouchesArgCaptor.firstValue.getMap(0)) .isEqualTo( JavaOnlyMap.of( "pageX", @@ -186,7 +195,7 @@ class RootViewTest { @Test fun testRemountApplication() { - val instanceManager = mock(ReactInstanceManager::class.java) + val instanceManager: ReactInstanceManager = mock() val rootView = ReactRootView(reactContext) rootView.startReactApplication(instanceManager, "") rootView.unmountReactApplication() @@ -195,7 +204,7 @@ class RootViewTest { @Test fun testCheckForKeyboardEvents() { - val instanceManager = mock(ReactInstanceManager::class.java) + val instanceManager: ReactInstanceManager = mock() val activity = Robolectric.buildActivity(Activity::class.java).create().get() whenever(instanceManager.currentReactContext).thenReturn(reactContext) val rootView: ReactRootView = diff --git a/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/uimanager/JSPointerDispatcherTest.kt b/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/uimanager/JSPointerDispatcherTest.kt index 8c6fe8760ec2..249c6825ccc1 100644 --- a/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/uimanager/JSPointerDispatcherTest.kt +++ b/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/uimanager/JSPointerDispatcherTest.kt @@ -22,9 +22,9 @@ import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.mockito.ArgumentMatcher -import org.mockito.Mockito.argThat -import org.mockito.Mockito.mock -import org.mockito.Mockito.verify +import org.mockito.kotlin.argThat +import org.mockito.kotlin.mock +import org.mockito.kotlin.verify import org.robolectric.RobolectricTestRunner import org.robolectric.RuntimeEnvironment @@ -75,7 +75,7 @@ class JSPointerDispatcherTest { val ev = createMotionEvent( MotionEvent.ACTION_DOWN, childRect.centerX().toFloat(), childRect.centerY().toFloat()) - val mockDispatcher: EventDispatcher = mock(EventDispatcher::class.java) + val mockDispatcher: EventDispatcher = mock() pointerDispatcher.handleMotionEvent(ev, mockDispatcher, false) verify(mockDispatcher).dispatchEvent(argThat(EventWithName(PointerEventHelper.POINTER_DOWN))) } From b54240af1e4a1e40a11c44bb49cf6cb190a8f243 Mon Sep 17 00:00:00 2001 From: Ruslan Shestopalyuk Date: Sat, 4 Jan 2025 03:38:16 -0800 Subject: [PATCH 3/5] Migrate RCTEventEmitter to Kotlin (#48467) Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/48467 # Changelog: [Internal] - As in the title. Differential Revision: D67793304 --- .../internal/interop/InteropEventEmitter.kt | 6 +-- .../uimanager/events/RCTEventEmitter.java | 43 ------------------ .../react/uimanager/events/RCTEventEmitter.kt | 44 +++++++++++++++++++ .../uimanager/events/ReactEventEmitter.kt | 4 +- .../bridge/interop/FakeRCTEventEmitter.kt | 2 +- 5 files changed, 50 insertions(+), 49 deletions(-) delete mode 100644 packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/events/RCTEventEmitter.java create mode 100644 packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/events/RCTEventEmitter.kt diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/interop/InteropEventEmitter.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/interop/InteropEventEmitter.kt index eca7be1f6832..c76b670251c5 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/interop/InteropEventEmitter.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/interop/InteropEventEmitter.kt @@ -31,12 +31,12 @@ public class InteropEventEmitter(private val reactContext: ReactContext) : RCTEv private var eventDispatcherOverride: EventDispatcher? = null @Deprecated("Deprecated in Java") - override fun receiveEvent(targetReactTag: Int, eventName: String, eventData: WritableMap?) { + override fun receiveEvent(targetTag: Int, eventName: String, params: WritableMap?) { val dispatcher: EventDispatcher? = eventDispatcherOverride - ?: UIManagerHelper.getEventDispatcherForReactTag(reactContext, targetReactTag) + ?: UIManagerHelper.getEventDispatcherForReactTag(reactContext, targetTag) val surfaceId = UIManagerHelper.getSurfaceId(reactContext) - dispatcher?.dispatchEvent(InteropEvent(eventName, eventData, surfaceId, targetReactTag)) + dispatcher?.dispatchEvent(InteropEvent(eventName, params, surfaceId, targetTag)) } @Deprecated("Deprecated in Java") diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/events/RCTEventEmitter.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/events/RCTEventEmitter.java deleted file mode 100644 index c51ed518ba56..000000000000 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/events/RCTEventEmitter.java +++ /dev/null @@ -1,43 +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.uimanager.events; - -import androidx.annotation.Nullable; -import com.facebook.proguard.annotations.DoNotStrip; -import com.facebook.react.bridge.JavaScriptModule; -import com.facebook.react.bridge.WritableArray; -import com.facebook.react.bridge.WritableMap; - -/** - * Paper JS interface to emit events from native to JS. - * - *

Deprecated in favor of RCTModernEventEmitter, which works with both the old and new renderer. - */ -@DoNotStrip -@Deprecated -public interface RCTEventEmitter extends JavaScriptModule { - /** - * @param targetReactTag react tag of the view that receives the event - * @param eventName name of event - * @param event event params - * @deprecated Use RCTModernEventEmitter.receiveEvent instead - */ - @Deprecated - void receiveEvent(int targetReactTag, String eventName, @Nullable WritableMap event); - - /** - * Receive and process touches - * - * @param eventName JS event name - * @param touches active pointers data - * @param changedIndices indices of changed pointers - * @deprecated Dispatch the TouchEvent using {@link EventDispatcher} instead - */ - @Deprecated - void receiveTouches(String eventName, WritableArray touches, WritableArray changedIndices); -} diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/events/RCTEventEmitter.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/events/RCTEventEmitter.kt new file mode 100644 index 000000000000..521c00fad6d6 --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/events/RCTEventEmitter.kt @@ -0,0 +1,44 @@ +/* + * 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.uimanager.events + +import com.facebook.proguard.annotations.DoNotStripAny +import com.facebook.react.bridge.JavaScriptModule +import com.facebook.react.bridge.WritableArray +import com.facebook.react.bridge.WritableMap + +/** + * Paper JS interface to emit events from native to JS. + * + * Deprecated in favor of [RCTModernEventEmitter], which works with both the old and new renderer. + */ +@DoNotStripAny +@Deprecated("Use [RCTModernEventEmitter] instead") +public interface RCTEventEmitter : JavaScriptModule { + /** + * @param targetTag react tag of the view that receives the event + * @param eventName name of event + * @param params event params + */ + @Deprecated("Use [RCTModernEventEmitter.receiveEvent] instead") + public fun receiveEvent(targetTag: Int, eventName: String, params: WritableMap?) + + /** + * Receive and process touches + * + * @param eventName JS event name + * @param touches active pointers data + * @param changedIndices indices of changed pointers + */ + @Deprecated("Dispatch the TouchEvent using [EventDispatcher] instead") + public fun receiveTouches( + eventName: String, + touches: WritableArray, + changedIndices: WritableArray + ) +} diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/events/ReactEventEmitter.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/events/ReactEventEmitter.kt index 07afeaf366e0..752de30ee7e7 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/events/ReactEventEmitter.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/events/ReactEventEmitter.kt @@ -45,8 +45,8 @@ internal class ReactEventEmitter(private val reactContext: ReactApplicationConte } @Deprecated("Please use RCTModernEventEmitter") - override fun receiveEvent(targetReactTag: Int, eventName: String, params: WritableMap?) { - receiveEvent(-1, targetReactTag, eventName, params) + override fun receiveEvent(targetTag: Int, eventName: String, params: WritableMap?) { + receiveEvent(-1, targetTag, eventName, params) } override fun receiveEvent( diff --git a/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/bridge/interop/FakeRCTEventEmitter.kt b/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/bridge/interop/FakeRCTEventEmitter.kt index f325893d0222..a2f1709c8e59 100644 --- a/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/bridge/interop/FakeRCTEventEmitter.kt +++ b/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/bridge/interop/FakeRCTEventEmitter.kt @@ -18,7 +18,7 @@ import com.facebook.react.uimanager.events.RCTEventEmitter class FakeRCTEventEmitter : RCTEventEmitter { @Deprecated("Deprecated in Java") - override fun receiveEvent(targetReactTag: Int, eventName: String, event: WritableMap?) = Unit + override fun receiveEvent(targetTag: Int, eventName: String, params: WritableMap?) = Unit @Deprecated("Deprecated in Java") override fun receiveTouches( From 2142479ff1f92ad9092e7e94a88d3e4cc604d5ad Mon Sep 17 00:00:00 2001 From: Ruslan Shestopalyuk Date: Sat, 4 Jan 2025 03:38:16 -0800 Subject: [PATCH 4/5] Migrate EventDispatcher interface to Kotlin (#48445) Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/48445 # Changelog: [Internal] - As in the title. Differential Revision: D67760373 --- .../uimanager/events/EventDispatcher.java | 37 ----------------- .../react/uimanager/events/EventDispatcher.kt | 40 +++++++++++++++++++ 2 files changed, 40 insertions(+), 37 deletions(-) delete mode 100644 packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/events/EventDispatcher.java create mode 100644 packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/events/EventDispatcher.kt diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/events/EventDispatcher.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/events/EventDispatcher.java deleted file mode 100644 index 056d671143cb..000000000000 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/events/EventDispatcher.java +++ /dev/null @@ -1,37 +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.uimanager.events; - -import com.facebook.react.uimanager.common.UIManagerType; - -public interface EventDispatcher { - - /** Sends the given Event to JS, coalescing eligible events if JS is backed up. */ - void dispatchEvent(Event event); - - void dispatchAllEvents(); - - /** Add a listener to this EventDispatcher. */ - void addListener(EventDispatcherListener listener); - - /** Remove a listener from this EventDispatcher. */ - void removeListener(EventDispatcherListener listener); - - void addBatchEventDispatchedListener(BatchEventDispatchedListener listener); - - void removeBatchEventDispatchedListener(BatchEventDispatchedListener listener); - - @Deprecated - void registerEventEmitter(@UIManagerType int uiManagerType, RCTEventEmitter eventEmitter); - - void registerEventEmitter(@UIManagerType int uiManagerType, RCTModernEventEmitter eventEmitter); - - void unregisterEventEmitter(@UIManagerType int uiManagerType); - - void onCatalystInstanceDestroyed(); -} diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/events/EventDispatcher.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/events/EventDispatcher.kt new file mode 100644 index 000000000000..cbb7b6233742 --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/events/EventDispatcher.kt @@ -0,0 +1,40 @@ +/* + * 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.uimanager.events + +import com.facebook.react.uimanager.common.UIManagerType + +public interface EventDispatcher { + /** Sends the given Event to JS, coalescing eligible events if JS is backed up. */ + public fun dispatchEvent(event: Event<*>) + + public fun dispatchAllEvents() + + /** Add a listener to this EventDispatcher. */ + public fun addListener(listener: EventDispatcherListener) + + /** Remove a listener from this EventDispatcher. */ + public fun removeListener(listener: EventDispatcherListener) + + public fun addBatchEventDispatchedListener(listener: BatchEventDispatchedListener) + + public fun removeBatchEventDispatchedListener(listener: BatchEventDispatchedListener) + + @Deprecated("Use the modern version with RCTModernEventEmitter") + @Suppress("DEPRECATION") + public fun registerEventEmitter(@UIManagerType uiManagerType: Int, eventEmitter: RCTEventEmitter) + + public fun registerEventEmitter( + @UIManagerType uiManagerType: Int, + eventEmitter: RCTModernEventEmitter + ) + + public fun unregisterEventEmitter(@UIManagerType uiManagerType: Int) + + public fun onCatalystInstanceDestroyed() +} From f91ef5249b12f930642da2aff4921b1c09d7c747 Mon Sep 17 00:00:00 2001 From: Ruslan Shestopalyuk Date: Sat, 4 Jan 2025 06:20:31 -0800 Subject: [PATCH 5/5] Convert FabricEventDispatcher Summary: # Changelog: [Internal] - As in the title. Differential Revision: D67825655 --- .../events/FabricEventDispatcher.java | 257 ------------------ .../uimanager/events/FabricEventDispatcher.kt | 241 ++++++++++++++++ 2 files changed, 241 insertions(+), 257 deletions(-) delete mode 100644 packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/events/FabricEventDispatcher.java create mode 100644 packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/events/FabricEventDispatcher.kt diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/events/FabricEventDispatcher.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/events/FabricEventDispatcher.java deleted file mode 100644 index 77a3c8e6c074..000000000000 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/events/FabricEventDispatcher.java +++ /dev/null @@ -1,257 +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.uimanager.events; - -import android.os.Handler; -import android.view.Choreographer; -import com.facebook.infer.annotation.Nullsafe; -import com.facebook.react.bridge.LifecycleEventListener; -import com.facebook.react.bridge.ReactApplicationContext; -import com.facebook.react.bridge.ReactNoCrashSoftException; -import com.facebook.react.bridge.ReactSoftExceptionLogger; -import com.facebook.react.bridge.UIManager; -import com.facebook.react.bridge.UiThreadUtil; -import com.facebook.react.internal.featureflags.ReactNativeFeatureFlags; -import com.facebook.react.modules.core.ReactChoreographer; -import com.facebook.react.uimanager.UIManagerHelper; -import com.facebook.react.uimanager.common.UIManagerType; -import com.facebook.systrace.Systrace; -import java.util.concurrent.CopyOnWriteArrayList; - -/** - * A singleton class that overrides {@link EventDispatcher} with no-op methods, to be used by - * callers that expect an EventDispatcher when the instance doesn't exist. - */ -@Nullsafe(Nullsafe.Mode.LOCAL) -public class FabricEventDispatcher implements EventDispatcher, LifecycleEventListener { - private static final Handler uiThreadHandler = UiThreadUtil.getUiThreadHandler(); - - private final ReactEventEmitter mReactEventEmitter; - private final ReactApplicationContext mReactContext; - private final CopyOnWriteArrayList mListeners = - new CopyOnWriteArrayList<>(); - private final CopyOnWriteArrayList mPostEventDispatchListeners = - new CopyOnWriteArrayList<>(); - private final FabricEventDispatcher.ScheduleDispatchFrameCallback mCurrentFrameCallback = - new FabricEventDispatcher.ScheduleDispatchFrameCallback(); - - private boolean mIsDispatchScheduled = false; - private final Runnable mDispatchEventsRunnable = - new Runnable() { - @Override - public void run() { - mIsDispatchScheduled = false; - - Systrace.beginSection( - Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, "BatchEventDispatchedListeners"); - try { - for (BatchEventDispatchedListener listener : mPostEventDispatchListeners) { - listener.onBatchEventDispatched(); - } - } finally { - Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE); - } - } - }; - - public FabricEventDispatcher(ReactApplicationContext reactContext) { - mReactContext = reactContext; - mReactContext.addLifecycleEventListener(this); - mReactEventEmitter = new ReactEventEmitter(mReactContext); - } - - @Override - public void dispatchEvent(Event event) { - for (EventDispatcherListener listener : mListeners) { - listener.onEventDispatch(event); - } - if (event.experimental_isSynchronous()) { - dispatchSynchronous(event); - } else { - event.dispatchModern(mReactEventEmitter); - } - - event.dispose(); - scheduleDispatchOfBatchedEvents(); - } - - private void dispatchSynchronous(Event event) { - Systrace.beginSection( - Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, - "FabricEventDispatcher.dispatchSynchronous('" + event.getEventName() + "')"); - try { - UIManager fabricUIManager = UIManagerHelper.getUIManager(mReactContext, UIManagerType.FABRIC); - if (fabricUIManager instanceof SynchronousEventReceiver) { - ((SynchronousEventReceiver) fabricUIManager) - .receiveEvent( - event.getSurfaceId(), - event.getViewTag(), - event.getEventName(), - event.canCoalesce(), - event.getEventData(), - event.getEventCategory(), - true); - } else { - ReactSoftExceptionLogger.logSoftException( - "FabricEventDispatcher", - new ReactNoCrashSoftException( - "Fabric UIManager expected to implement SynchronousEventReceiver.")); - } - } finally { - Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE); - } - } - - public void dispatchAllEvents() { - scheduleDispatchOfBatchedEvents(); - } - - private void scheduleDispatchOfBatchedEvents() { - if (ReactNativeFeatureFlags.useOptimizedEventBatchingOnAndroid()) { - if (!mIsDispatchScheduled) { - mIsDispatchScheduled = true; - uiThreadHandler.postAtFrontOfQueue(mDispatchEventsRunnable); - } - } else { - mCurrentFrameCallback.maybeScheduleDispatchOfBatchedEvents(); - } - } - - /** Add a listener to this EventDispatcher. */ - public void addListener(EventDispatcherListener listener) { - mListeners.add(listener); - } - - /** Remove a listener from this EventDispatcher. */ - public void removeListener(EventDispatcherListener listener) { - mListeners.remove(listener); - } - - public void addBatchEventDispatchedListener(BatchEventDispatchedListener listener) { - mPostEventDispatchListeners.add(listener); - } - - public void removeBatchEventDispatchedListener(BatchEventDispatchedListener listener) { - mPostEventDispatchListeners.remove(listener); - } - - @Override - public void onHostResume() { - scheduleDispatchOfBatchedEvents(); - if (!ReactNativeFeatureFlags.useOptimizedEventBatchingOnAndroid()) { - mCurrentFrameCallback.resume(); - } - } - - @Override - public void onHostPause() { - cancelDispatchOfBatchedEvents(); - } - - @Override - public void onHostDestroy() { - cancelDispatchOfBatchedEvents(); - } - - public void onCatalystInstanceDestroyed() { - UiThreadUtil.runOnUiThread( - new Runnable() { - @Override - public void run() { - cancelDispatchOfBatchedEvents(); - } - }); - } - - private void cancelDispatchOfBatchedEvents() { - UiThreadUtil.assertOnUiThread(); - if (ReactNativeFeatureFlags.useOptimizedEventBatchingOnAndroid()) { - mIsDispatchScheduled = false; - uiThreadHandler.removeCallbacks(mDispatchEventsRunnable); - } else { - mCurrentFrameCallback.stop(); - } - } - - public void registerEventEmitter(@UIManagerType int uiManagerType, RCTEventEmitter eventEmitter) { - mReactEventEmitter.register(uiManagerType, eventEmitter); - } - - public void registerEventEmitter( - @UIManagerType int uiManagerType, RCTModernEventEmitter eventEmitter) { - mReactEventEmitter.register(uiManagerType, eventEmitter); - } - - public void unregisterEventEmitter(@UIManagerType int uiManagerType) { - mReactEventEmitter.unregister(uiManagerType); - } - - private class ScheduleDispatchFrameCallback implements Choreographer.FrameCallback { - private volatile boolean mIsDispatchScheduled = false; - private boolean mShouldStop = false; - - @Override - public void doFrame(long frameTimeNanos) { - UiThreadUtil.assertOnUiThread(); - - if (mShouldStop) { - mIsDispatchScheduled = false; - } else { - dispatchBatchedEvents(); - } - - Systrace.beginSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, "BatchEventDispatchedListeners"); - try { - for (BatchEventDispatchedListener listener : mPostEventDispatchListeners) { - listener.onBatchEventDispatched(); - } - } finally { - Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE); - } - } - - public void stop() { - mShouldStop = true; - } - - public void resume() { - mShouldStop = false; - } - - public void maybeDispatchBatchedEvents() { - if (!mIsDispatchScheduled) { - mIsDispatchScheduled = true; - dispatchBatchedEvents(); - } - } - - private void dispatchBatchedEvents() { - ReactChoreographer.getInstance() - .postFrameCallback(ReactChoreographer.CallbackType.TIMERS_EVENTS, mCurrentFrameCallback); - } - - public void maybeScheduleDispatchOfBatchedEvents() { - if (mIsDispatchScheduled) { - return; - } - - // We should only hit this slow path when we receive events while the host activity is paused. - if (mReactContext.isOnUiQueueThread()) { - maybeDispatchBatchedEvents(); - } else { - mReactContext.runOnUiQueueThread( - new Runnable() { - @Override - public void run() { - maybeDispatchBatchedEvents(); - } - }); - } - } - } -} diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/events/FabricEventDispatcher.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/events/FabricEventDispatcher.kt new file mode 100644 index 000000000000..d5e34a493d1f --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/events/FabricEventDispatcher.kt @@ -0,0 +1,241 @@ +/* + * 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.uimanager.events + +import android.os.Handler +import android.view.Choreographer +import com.facebook.react.bridge.LifecycleEventListener +import com.facebook.react.bridge.ReactApplicationContext +import com.facebook.react.bridge.ReactNoCrashSoftException +import com.facebook.react.bridge.ReactSoftExceptionLogger +import com.facebook.react.bridge.UIManager +import com.facebook.react.bridge.UiThreadUtil +import com.facebook.react.internal.featureflags.ReactNativeFeatureFlags +import com.facebook.react.modules.core.ReactChoreographer +import com.facebook.react.uimanager.UIManagerHelper +import com.facebook.react.uimanager.common.UIManagerType +import com.facebook.systrace.Systrace +import java.util.concurrent.CopyOnWriteArrayList +import kotlin.concurrent.Volatile + +/** + * A singleton class that overrides [EventDispatcher] with no-op methods, to be used by callers that + * expect an EventDispatcher when the instance doesn't exist. + */ +public open class FabricEventDispatcher(reactContext: ReactApplicationContext) : + EventDispatcher, LifecycleEventListener { + private val reactEventEmitter: ReactEventEmitter + private val reactContext: ReactApplicationContext = reactContext + private val listeners = CopyOnWriteArrayList() + private val postEventDispatchListeners = CopyOnWriteArrayList() + private val currentFrameCallback: ScheduleDispatchFrameCallback = ScheduleDispatchFrameCallback() + + private var isDispatchScheduled = false + private val dispatchEventsRunnable = Runnable { + isDispatchScheduled = false + Systrace.beginSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, "BatchEventDispatchedListeners") + try { + for (listener in postEventDispatchListeners) { + listener.onBatchEventDispatched() + } + } finally { + Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE) + } + } + + init { + this.reactContext.addLifecycleEventListener(this) + reactEventEmitter = ReactEventEmitter(this.reactContext) + } + + public override fun dispatchEvent(event: Event<*>) { + for (listener in listeners) { + listener.onEventDispatch(event) + } + if (event.experimental_isSynchronous()) { + dispatchSynchronous(event) + } else { + event.dispatchModern(reactEventEmitter) + } + + event.dispose() + scheduleDispatchOfBatchedEvents() + } + + private fun dispatchSynchronous(event: Event<*>) { + Systrace.beginSection( + Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, + "FabricEventDispatcher.dispatchSynchronous('" + event.eventName + "')") + try { + val fabricUIManager: UIManager? = + UIManagerHelper.getUIManager(reactContext, UIManagerType.FABRIC) + @Suppress("DEPRECATION") + if (fabricUIManager is SynchronousEventReceiver) { + @Suppress("DEPRECATION") + (fabricUIManager as SynchronousEventReceiver).receiveEvent( + event.surfaceId, + event.viewTag, + event.eventName, + event.canCoalesce(), + event.eventData, + event.eventCategory, + true) + } else { + ReactSoftExceptionLogger.logSoftException( + "FabricEventDispatcher", + ReactNoCrashSoftException( + "Fabric UIManager expected to implement SynchronousEventReceiver.")) + } + } finally { + Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE) + } + } + + public override fun dispatchAllEvents() { + scheduleDispatchOfBatchedEvents() + } + + private fun scheduleDispatchOfBatchedEvents() { + if (ReactNativeFeatureFlags.useOptimizedEventBatchingOnAndroid()) { + if (!isDispatchScheduled) { + isDispatchScheduled = true + uiThreadHandler.postAtFrontOfQueue(dispatchEventsRunnable) + } + } else { + currentFrameCallback.maybeScheduleDispatchOfBatchedEvents() + } + } + + /** Add a listener to this EventDispatcher. */ + public override fun addListener(listener: EventDispatcherListener) { + listeners.add(listener) + } + + /** Remove a listener from this EventDispatcher. */ + public override fun removeListener(listener: EventDispatcherListener) { + listeners.remove(listener) + } + + public override fun addBatchEventDispatchedListener(listener: BatchEventDispatchedListener) { + postEventDispatchListeners.add(listener) + } + + public override fun removeBatchEventDispatchedListener(listener: BatchEventDispatchedListener) { + postEventDispatchListeners.remove(listener) + } + + public override fun onHostResume() { + scheduleDispatchOfBatchedEvents() + if (!ReactNativeFeatureFlags.useOptimizedEventBatchingOnAndroid()) { + currentFrameCallback.resume() + } + } + + public override fun onHostPause() { + cancelDispatchOfBatchedEvents() + } + + public override fun onHostDestroy() { + cancelDispatchOfBatchedEvents() + } + + public override fun onCatalystInstanceDestroyed() { + UiThreadUtil.runOnUiThread(Runnable { cancelDispatchOfBatchedEvents() }) + } + + private fun cancelDispatchOfBatchedEvents() { + UiThreadUtil.assertOnUiThread() + if (ReactNativeFeatureFlags.useOptimizedEventBatchingOnAndroid()) { + isDispatchScheduled = false + uiThreadHandler.removeCallbacks(dispatchEventsRunnable) + } else { + currentFrameCallback.stop() + } + } + + @Deprecated("Use the modern version with RCTModernEventEmitter") + @Suppress("DEPRECATION") + public override fun registerEventEmitter( + @UIManagerType uiManagerType: Int, + eventEmitter: RCTEventEmitter + ) { + reactEventEmitter.register(uiManagerType, eventEmitter) + } + + public override fun registerEventEmitter( + @UIManagerType uiManagerType: Int, + eventEmitter: RCTModernEventEmitter + ) { + reactEventEmitter.register(uiManagerType, eventEmitter) + } + + public override fun unregisterEventEmitter(@UIManagerType uiManagerType: Int) { + reactEventEmitter.unregister(uiManagerType) + } + + private inner class ScheduleDispatchFrameCallback : Choreographer.FrameCallback { + @Volatile private var isFrameCallbackDispatchScheduled = false + private var shouldStop = false + + override fun doFrame(frameTimeNanos: Long) { + UiThreadUtil.assertOnUiThread() + + if (shouldStop) { + isFrameCallbackDispatchScheduled = false + } else { + dispatchBatchedEvents() + } + + Systrace.beginSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, "BatchEventDispatchedListeners") + try { + for (listener in postEventDispatchListeners) { + listener.onBatchEventDispatched() + } + } finally { + Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE) + } + } + + public fun stop() { + shouldStop = true + } + + public fun resume() { + shouldStop = false + } + + public fun maybeDispatchBatchedEvents() { + if (!isFrameCallbackDispatchScheduled) { + isFrameCallbackDispatchScheduled = true + dispatchBatchedEvents() + } + } + + private fun dispatchBatchedEvents() { + ReactChoreographer.getInstance() + .postFrameCallback(ReactChoreographer.CallbackType.TIMERS_EVENTS, currentFrameCallback) + } + + public fun maybeScheduleDispatchOfBatchedEvents() { + if (isFrameCallbackDispatchScheduled) { + return + } + + // We should only hit this slow path when we receive events while the host activity is paused. + if (reactContext.isOnUiQueueThread()) { + maybeDispatchBatchedEvents() + } else { + reactContext.runOnUiQueueThread(Runnable { maybeDispatchBatchedEvents() }) + } + } + } + + private companion object { + private val uiThreadHandler: Handler = UiThreadUtil.getUiThreadHandler() + } +}