Refactor Event to encourage and ease migration to `RCTModernEventEmitter` support

Summary:
The `Event` interface has been improved such that:

1. `getEventData` is now a default method on Event that returns null
2. `dispatch` has a default implementation that relies on getEventName() and getEventData()
3. `dispatchModern` can detect if surfaceId and event data are present; if not, it falls back to dispatch

This will dramatically ease future migrations: at some point in the (distant) future, we can simply delete RCTEventEmitter and
all use-cases will be supported by the current `Event` class without needing to introduce a 3rd transitional interface; and 99%
of all Event classes can be simplified, delet their `dispatch` implementation and need no further work.

At their core, all Events are simply: (1) a name, (2) data, (3) a target (surfaceId and tag). The interface now reflects that but still
allows for flexibility of the data and names being generated on-demand if necessary; but for the vast majority of Event classes, code
will be dramatically simplified.

I also migrate a single Event class, ContentSizeChangeEvent, to use this new method of dispatch.

Changelog: [Android][Changed] Added convenience methods to simplify native Event classes and ease migrations

Reviewed By: mdvacca

Differential Revision: D26043325

fbshipit-source-id: bc308105f7f6e654d45fd156dbf4a2bcbc45819c
This commit is contained in:
Joshua Gross 2021-01-28 14:01:07 -08:00 коммит произвёл Facebook GitHub Bot
Родитель de8aa2c6b9
Коммит 72d0ddc16f
3 изменённых файлов: 58 добавлений и 25 удалений

Просмотреть файл

@ -19,8 +19,13 @@ public class ContentSizeChangeEvent extends Event<ContentSizeChangeEvent> {
private final int mWidth; private final int mWidth;
private final int mHeight; private final int mHeight;
@Deprecated
public ContentSizeChangeEvent(int viewTag, int width, int height) { public ContentSizeChangeEvent(int viewTag, int width, int height) {
super(viewTag); this(-1, viewTag, width, height);
}
public ContentSizeChangeEvent(int surfaceId, int viewTag, int width, int height) {
super(surfaceId, viewTag);
mWidth = width; mWidth = width;
mHeight = height; mHeight = height;
} }
@ -31,10 +36,10 @@ public class ContentSizeChangeEvent extends Event<ContentSizeChangeEvent> {
} }
@Override @Override
public void dispatch(RCTEventEmitter rctEventEmitter) { protected WritableMap getEventData() {
WritableMap data = Arguments.createMap(); WritableMap data = Arguments.createMap();
data.putDouble("width", PixelUtil.toDIPFromPixel(mWidth)); data.putDouble("width", PixelUtil.toDIPFromPixel(mWidth));
data.putDouble("height", PixelUtil.toDIPFromPixel(mHeight)); data.putDouble("height", PixelUtil.toDIPFromPixel(mHeight));
rctEventEmitter.receiveEvent(getViewTag(), EVENT_NAME, data); return data;
} }
} }

Просмотреть файл

@ -7,14 +7,26 @@
package com.facebook.react.uimanager.events; package com.facebook.react.uimanager.events;
import androidx.annotation.Nullable;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.common.SystemClock; import com.facebook.react.common.SystemClock;
import com.facebook.react.uimanager.IllegalViewOperationException;
/** /**
* A UI event that can be dispatched to JS. * A UI event that can be dispatched to JS.
* *
* <p>For dispatching events {@link EventDispatcher#dispatchEvent} should be used. Once event object * <p>For dispatching events {@code getEventData} should be used. Once event object is passed to the
* is passed to the EventDispatched it should no longer be used as EventDispatcher may decide to * EventDispatched it should no longer be used as EventDispatcher may decide to recycle that object
* recycle that object (by calling {@link #dispose}). * (by calling {@link #dispose}).
*
* <p>If you need advanced customizations and overriding only {@code getEventData} doesn't work for
* you, you must override both {@code dispatch} and {@code dispatchModern}. Both of these will be
* deleted in the distant future and it is highly recommended to use only {@code getEventData}.
*
* <p>Old, pre-Fabric Events only used viewTag as the identifier, but Fabric needs surfaceId as well
* as viewTag. You may use {@code UIManagerHelper.getSurfaceId} on a Fabric-managed View to get the
* surfaceId. Fabric will work without surfaceId - making {@code Event} backwards-compatible - but
* Events without SurfaceId are slightly slower to propagate.
*/ */
public abstract class Event<T extends Event> { public abstract class Event<T extends Event> {
@ -119,15 +131,45 @@ public abstract class Event<T extends Event> {
/** /**
* Dispatch this event to JS using the given event emitter. Compatible with old and new renderer. * Dispatch this event to JS using the given event emitter. Compatible with old and new renderer.
* Instead of using this or dispatchModern, it is recommended that you simply override
* `getEventData`. In the future
*/ */
@Deprecated @Deprecated
public abstract void dispatch(RCTEventEmitter rctEventEmitter); public void dispatch(RCTEventEmitter rctEventEmitter) {
WritableMap eventData = getEventData();
if (eventData == null) {
throw new IllegalViewOperationException(
"Event: you must return a valid, non-null value from `getEventData`, or override `dispatch` and `disatchModern`. Event: "
+ getEventName());
}
rctEventEmitter.receiveEvent(getViewTag(), getEventName(), eventData);
}
/** /**
* Dispatch this event to JS using a V2 EventEmitter. Events must explicitly override this, by * Can be overridden by classes to make migrating to RCTModernEventEmitter support easier. If this
* default it uses the V1 dispatcher. * class returns null, the RCTEventEmitter interface will be used instead of
* RCTModernEventEmitter. In the future, returning null here will be an error.
*/ */
@Nullable
protected WritableMap getEventData() {
return null;
}
/**
* Dispatch this event to JS using a V2 EventEmitter. If surfaceId is not -1 and `getEventData` is
* non-null, this will use the RCTModernEventEmitter API. Otherwise, it falls back to the
* old-style dispatch function. For Event classes that need to do something different, this method
* can always be overridden entirely, but it is not recommended.
*/
@Deprecated
public void dispatchModern(RCTModernEventEmitter rctEventEmitter) { public void dispatchModern(RCTModernEventEmitter rctEventEmitter) {
if (getSurfaceId() != -1) {
WritableMap eventData = getEventData();
if (eventData != null) {
rctEventEmitter.receiveEvent(getSurfaceId(), getViewTag(), getEventName(), getEventData());
return;
}
}
dispatch(rctEventEmitter); dispatch(rctEventEmitter);
} }
} }

Просмотреть файл

@ -12,8 +12,6 @@ import androidx.annotation.Nullable;
import com.facebook.react.bridge.Arguments; import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.WritableMap; import com.facebook.react.bridge.WritableMap;
import com.facebook.react.uimanager.events.Event; import com.facebook.react.uimanager.events.Event;
import com.facebook.react.uimanager.events.RCTEventEmitter;
import com.facebook.react.uimanager.events.RCTModernEventEmitter;
import java.lang.annotation.Retention; import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy; import java.lang.annotation.RetentionPolicy;
@ -146,30 +144,18 @@ public class ImageLoadEvent extends Event<ImageLoadEvent> {
} }
@Override @Override
public void dispatch(RCTEventEmitter rctEventEmitter) { protected WritableMap getEventData() {
rctEventEmitter.receiveEvent(getViewTag(), getEventName(), getEventData()); WritableMap eventData = Arguments.createMap();
}
@Override
public void dispatchModern(RCTModernEventEmitter rctEventEmitter) {
rctEventEmitter.receiveEvent(getSurfaceId(), getViewTag(), getEventName(), getEventData());
}
private WritableMap getEventData() {
WritableMap eventData = null;
switch (mEventType) { switch (mEventType) {
case ON_PROGRESS: case ON_PROGRESS:
eventData = Arguments.createMap();
eventData.putInt("loaded", mLoaded); eventData.putInt("loaded", mLoaded);
eventData.putInt("total", mTotal); eventData.putInt("total", mTotal);
break; break;
case ON_LOAD: case ON_LOAD:
eventData = Arguments.createMap();
eventData.putMap("source", createEventDataSource()); eventData.putMap("source", createEventDataSource());
break; break;
case ON_ERROR: case ON_ERROR:
eventData = Arguments.createMap();
eventData.putString("error", mErrorMessage); eventData.putString("error", mErrorMessage);
break; break;
} }