mirror of
https://github.com/facebook/react-native.git
synced 2025-11-01 09:14:26 +00:00
Queue the event for preallocated but not mounted view to dispatch later
Summary: This diff fixed an edge case that event dispatching is failed after pre-allocation of a view and before the view is mounted. When a cached image is loaded, we will dispatch the event to JS immediately. This is could happen after the view is created during pre-allocation phase, when the event emitter is not instantiated yet. In that case, we will see [an error](https://github.com/facebook/react-native/blob/main/ReactAndroid/src/main/java/com/facebook/react/fabric/FabricUIManager.java#L927) and the event will effectively be ignored. To fix that we introduced a queue in this diff for those events. They will be dispatched in order when the view is mounted and the event emitter is non-null. Changelog: [Android][Fixed] - Fixed an edge case that event dispatching is failed after pre-allocation of a view and before the view is mounted. Reviewed By: mullender Differential Revision: D36331914 fbshipit-source-id: cd065b0b36978cb5f0aac793d8d16f07a48f0881
This commit is contained in:
committed by
Facebook GitHub Bot
parent
644fe430fd
commit
a093fe5f2f
@@ -111,6 +111,9 @@ public class ReactFeatureFlags {
|
||||
|
||||
public static boolean dispatchPointerEvents = false;
|
||||
|
||||
/** Feature Flag to enable the pending event queue in fabric before mounting views */
|
||||
public static boolean enableFabricPendingEventQueue = false;
|
||||
|
||||
/** Feature Flag to control RN Android scrollEventThrottle prop. */
|
||||
public static boolean enableScrollEventThrottle = false;
|
||||
}
|
||||
|
||||
@@ -59,6 +59,7 @@ import com.facebook.react.fabric.events.FabricEventEmitter;
|
||||
import com.facebook.react.fabric.mounting.MountItemDispatcher;
|
||||
import com.facebook.react.fabric.mounting.MountingManager;
|
||||
import com.facebook.react.fabric.mounting.SurfaceMountingManager;
|
||||
import com.facebook.react.fabric.mounting.SurfaceMountingManager.ViewEvent;
|
||||
import com.facebook.react.fabric.mounting.mountitems.DispatchIntCommandMountItem;
|
||||
import com.facebook.react.fabric.mounting.mountitems.DispatchStringCommandMountItem;
|
||||
import com.facebook.react.fabric.mounting.mountitems.IntBufferBatchMountItem;
|
||||
@@ -923,8 +924,18 @@ public class FabricUIManager implements UIManager, LifecycleEventListener {
|
||||
EventEmitterWrapper eventEmitter = mMountingManager.getEventEmitter(surfaceId, reactTag);
|
||||
|
||||
if (eventEmitter == null) {
|
||||
// This can happen if the view has disappeared from the screen (because of async events)
|
||||
FLog.d(TAG, "Unable to invoke event: " + eventName + " for reactTag: " + reactTag);
|
||||
if (ReactFeatureFlags.enableFabricPendingEventQueue
|
||||
&& mMountingManager.getViewExists(reactTag)) {
|
||||
// The view is preallocated and created. However, it hasn't been mounted yet. We will have
|
||||
// access to the event emitter later when the view is mounted. For now just save the event
|
||||
// in the view state and trigger it later.
|
||||
mMountingManager.enqueuePendingEvent(
|
||||
reactTag,
|
||||
new ViewEvent(eventName, params, eventCategory, canCoalesceEvent, customCoalesceKey));
|
||||
} else {
|
||||
// This can happen if the view has disappeared from the screen (because of async events)
|
||||
FLog.d(TAG, "Unable to invoke event: " + eventName + " for reactTag: " + reactTag);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -26,6 +26,7 @@ import com.facebook.react.bridge.UiThreadUtil;
|
||||
import com.facebook.react.common.mapbuffer.MapBuffer;
|
||||
import com.facebook.react.fabric.FabricUIManager;
|
||||
import com.facebook.react.fabric.events.EventEmitterWrapper;
|
||||
import com.facebook.react.fabric.mounting.SurfaceMountingManager.ViewEvent;
|
||||
import com.facebook.react.fabric.mounting.mountitems.MountItem;
|
||||
import com.facebook.react.touch.JSResponderHandler;
|
||||
import com.facebook.react.uimanager.RootViewManager;
|
||||
@@ -422,4 +423,14 @@ public class MountingManager {
|
||||
public void initializeViewManager(String componentName) {
|
||||
mViewManagerRegistry.get(componentName);
|
||||
}
|
||||
|
||||
public void enqueuePendingEvent(int reactTag, ViewEvent viewEvent) {
|
||||
@Nullable SurfaceMountingManager smm = getSurfaceManagerForView(reactTag);
|
||||
if (smm == null) {
|
||||
// Cannot queue event without valid surface mountng manager. Do nothing here.
|
||||
return;
|
||||
}
|
||||
|
||||
smm.enqueuePendingEvent(reactTag, viewEvent);
|
||||
}
|
||||
}
|
||||
|
||||
+76
@@ -25,6 +25,7 @@ import com.facebook.react.bridge.ReadableMap;
|
||||
import com.facebook.react.bridge.RetryableMountingLayerException;
|
||||
import com.facebook.react.bridge.SoftAssertions;
|
||||
import com.facebook.react.bridge.UiThreadUtil;
|
||||
import com.facebook.react.bridge.WritableMap;
|
||||
import com.facebook.react.common.build.ReactBuildConfig;
|
||||
import com.facebook.react.common.mapbuffer.ReadableMapBuffer;
|
||||
import com.facebook.react.config.ReactFeatureFlags;
|
||||
@@ -43,9 +44,12 @@ import com.facebook.react.uimanager.ThemedReactContext;
|
||||
import com.facebook.react.uimanager.ViewGroupManager;
|
||||
import com.facebook.react.uimanager.ViewManager;
|
||||
import com.facebook.react.uimanager.ViewManagerRegistry;
|
||||
import com.facebook.react.uimanager.events.EventCategoryDef;
|
||||
import com.facebook.react.views.view.ReactMapBufferViewManager;
|
||||
import com.facebook.react.views.view.ReactViewManagerWrapper;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedList;
|
||||
import java.util.Queue;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
@@ -849,6 +853,7 @@ public class SurfaceMountingManager {
|
||||
}
|
||||
}
|
||||
|
||||
/** We update the event emitter from the main thread when the view is mounted. */
|
||||
@UiThread
|
||||
public void updateEventEmitter(int reactTag, @NonNull EventEmitterWrapper eventEmitter) {
|
||||
UiThreadUtil.assertOnUiThread();
|
||||
@@ -870,6 +875,20 @@ public class SurfaceMountingManager {
|
||||
if (previousEventEmitterWrapper != eventEmitter && previousEventEmitterWrapper != null) {
|
||||
previousEventEmitterWrapper.destroy();
|
||||
}
|
||||
|
||||
if (viewState.mPendingEventQueue != null) {
|
||||
// Invoke pending event queued to the view state
|
||||
for (ViewEvent viewEvent : viewState.mPendingEventQueue) {
|
||||
if (viewEvent.canCoalesceEvent()) {
|
||||
eventEmitter.invokeUnique(
|
||||
viewEvent.getEventName(), viewEvent.getParams(), viewEvent.getCustomCoalesceKey());
|
||||
} else {
|
||||
eventEmitter.invoke(
|
||||
viewEvent.getEventName(), viewEvent.getParams(), viewEvent.getEventCategory());
|
||||
}
|
||||
}
|
||||
viewState.mPendingEventQueue = null;
|
||||
}
|
||||
}
|
||||
|
||||
@UiThread
|
||||
@@ -1065,6 +1084,21 @@ public class SurfaceMountingManager {
|
||||
}
|
||||
}
|
||||
|
||||
@UiThread
|
||||
public void enqueuePendingEvent(int reactTag, ViewEvent viewEvent) {
|
||||
UiThreadUtil.assertOnUiThread();
|
||||
|
||||
ViewState viewState = mTagToViewState.get(reactTag);
|
||||
if (viewState == null) {
|
||||
// Cannot queue event without view state. Do nothing here.
|
||||
return;
|
||||
}
|
||||
if (viewState.mPendingEventQueue == null) {
|
||||
viewState.mPendingEventQueue = new LinkedList<>();
|
||||
}
|
||||
viewState.mPendingEventQueue.add(viewEvent);
|
||||
}
|
||||
|
||||
/**
|
||||
* This class holds view state for react tags. Objects of this class are stored into the {@link
|
||||
* #mTagToViewState}, and they should be updated in the same thread.
|
||||
@@ -1078,6 +1112,7 @@ public class SurfaceMountingManager {
|
||||
@Nullable public ReadableMap mCurrentLocalData = null;
|
||||
@Nullable public StateWrapper mStateWrapper = null;
|
||||
@Nullable public EventEmitterWrapper mEventEmitter = null;
|
||||
@Nullable public Queue<ViewEvent> mPendingEventQueue = null;
|
||||
|
||||
private ViewState(
|
||||
int reactTag, @Nullable View view, @Nullable ReactViewManagerWrapper viewManager) {
|
||||
@@ -1112,4 +1147,45 @@ public class SurfaceMountingManager {
|
||||
+ isLayoutOnly;
|
||||
}
|
||||
}
|
||||
|
||||
public static class ViewEvent {
|
||||
private final String mEventName;
|
||||
private final boolean mCanCoalesceEvent;
|
||||
private final int mCustomCoalesceKey;
|
||||
private final @EventCategoryDef int mEventCategory;
|
||||
private @Nullable WritableMap mParams;
|
||||
|
||||
public ViewEvent(
|
||||
String eventName,
|
||||
@Nullable WritableMap params,
|
||||
@EventCategoryDef int eventCategory,
|
||||
boolean canCoalesceEvent,
|
||||
int customCoalesceKey) {
|
||||
mEventName = eventName;
|
||||
mParams = params;
|
||||
mEventCategory = eventCategory;
|
||||
mCanCoalesceEvent = canCoalesceEvent;
|
||||
mCustomCoalesceKey = customCoalesceKey;
|
||||
}
|
||||
|
||||
public String getEventName() {
|
||||
return mEventName;
|
||||
}
|
||||
|
||||
public boolean canCoalesceEvent() {
|
||||
return mCanCoalesceEvent;
|
||||
}
|
||||
|
||||
public int getCustomCoalesceKey() {
|
||||
return mCustomCoalesceKey;
|
||||
}
|
||||
|
||||
public @EventCategoryDef int getEventCategory() {
|
||||
return mEventCategory;
|
||||
}
|
||||
|
||||
public @Nullable WritableMap getParams() {
|
||||
return mParams;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user