RN Android: Support View Manager Commands that are strings

Summary:
Right now JS triggers a view manager command with the following code:

```
UIManager.dispatchViewManagerCommand(
  ReactNative.findNodeHandle(this),
  UIManager.getViewManagerConfig('RCTView').Commands.hotspotUpdate,
  [destX || 0, destY || 0],
);
```

As we want to get rid of calls to UIManager, we need to stop looking for the integer defined in native from JavaScript. We will be changing methods like this to be:

```
UIManager.dispatchViewManagerCommand(
  ReactNative.findNodeHandle(this),
  'hotspotUpdate',
  [destX || 0, destY || 0],
);
```

We need to support ints and Strings to be backwards compatible, but ints will be deprecated.

Reviewed By: shergin

Differential Revision: D15955444

fbshipit-source-id: d1c488975ae03404f8f851a7035b58a90ed34163
This commit is contained in:
Eli White 2019-06-24 18:42:16 -07:00 коммит произвёл Facebook Github Bot
Родитель 4b8d07ebf3
Коммит 3cae6fa950
11 изменённых файлов: 187 добавлений и 17 удалений

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

@ -32,12 +32,24 @@ public interface UIManager extends JSIModule, PerformanceCounter {
* 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.
*
* @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.
*
* @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);
void setJSResponder(int reactTag, boolean blockNativeResponder);
void clearJSResponder();

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

@ -18,6 +18,7 @@ import com.facebook.react.fabric.mounting.ViewPool;
import com.facebook.react.fabric.mounting.mountitems.BatchMountItem;
import com.facebook.react.fabric.mounting.mountitems.DeleteMountItem;
import com.facebook.react.fabric.mounting.mountitems.DispatchCommandMountItem;
import com.facebook.react.fabric.mounting.mountitems.DispatchStringCommandMountItem;
import com.facebook.react.fabric.mounting.mountitems.InsertMountItem;
import com.facebook.react.fabric.mounting.mountitems.MountItem;
import com.facebook.react.fabric.mounting.mountitems.PreAllocateViewMountItem;
@ -102,6 +103,7 @@ public class FabricJSIModuleProvider implements JSIModuleProvider<UIManager> {
BatchMountItem.class.getClass();
DeleteMountItem.class.getClass();
DispatchCommandMountItem.class.getClass();
DispatchStringCommandMountItem.class.getClass();
InsertMountItem.class.getClass();
MountItem.class.getClass();
RemoveMountItem.class.getClass();

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

@ -44,6 +44,7 @@ import com.facebook.react.fabric.mounting.mountitems.BatchMountItem;
import com.facebook.react.fabric.mounting.mountitems.CreateMountItem;
import com.facebook.react.fabric.mounting.mountitems.DeleteMountItem;
import com.facebook.react.fabric.mounting.mountitems.DispatchCommandMountItem;
import com.facebook.react.fabric.mounting.mountitems.DispatchStringCommandMountItem;
import com.facebook.react.fabric.mounting.mountitems.InsertMountItem;
import com.facebook.react.fabric.mounting.mountitems.MountItem;
import com.facebook.react.fabric.mounting.mountitems.PreAllocateViewMountItem;
@ -486,6 +487,14 @@ public class FabricUIManager implements UIManager, LifecycleEventListener {
}
}
@Override
public void dispatchCommand(
final int reactTag, final String commandId, @Nullable final ReadableArray commandArgs) {
synchronized (mMountItemsLock) {
mMountItems.add(new DispatchStringCommandMountItem(reactTag, commandId, commandArgs));
}
}
@Override
public void setJSResponder(int reactTag, boolean blockNativeResponder) {
// do nothing for now.

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

@ -148,6 +148,20 @@ public class MountingManager {
viewState.mViewManager.receiveCommand(viewState.mView, commandId, commandArgs);
}
public void receiveCommand(int reactTag, String commandId, @Nullable ReadableArray commandArgs) {
ViewState viewState = getViewState(reactTag);
if (viewState.mViewManager == null) {
throw new IllegalStateException("Unable to find viewState manager for tag " + reactTag);
}
if (viewState.mView == null) {
throw new IllegalStateException("Unable to find viewState view for tag " + reactTag);
}
viewState.mViewManager.receiveCommand(viewState.mView, commandId, commandArgs);
}
@SuppressWarnings("unchecked") // prevents unchecked conversion warn of the <ViewGroup> type
private static ViewGroupManager<ViewGroup> getViewGroupManager(ViewState viewState) {
if (viewState.mViewManager == null) {

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

@ -0,0 +1,36 @@
/**
* Copyright (c) 2014-present, Facebook, Inc.
*
* <p>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.fabric.mounting.mountitems;
import androidx.annotation.Nullable;
import com.facebook.react.fabric.mounting.MountingManager;
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.bridge.UiThreadUtil;
public class DispatchStringCommandMountItem implements MountItem {
private final int mReactTag;
private final String mCommandId;
private final @Nullable ReadableArray mCommandArgs;
public DispatchStringCommandMountItem(
int reactTag, String commandId, @Nullable ReadableArray commandArgs) {
mReactTag = reactTag;
mCommandId = commandId;
mCommandArgs = commandArgs;
}
@Override
public void execute(MountingManager mountingManager) {
mountingManager.receiveCommand(mReactTag, mCommandId, mCommandArgs);
}
@Override
public String toString() {
return "DispatchStringCommandMountItem [" + mReactTag + "] " + mCommandId;
}
}

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

@ -770,6 +770,21 @@ public class NativeViewHierarchyManager {
viewManager.receiveCommand(view, commandId, args);
}
public synchronized void dispatchCommand(
int reactTag,
String commandId,
@Nullable ReadableArray args) {
UiThreadUtil.assertOnUiThread();
View view = mTagsToViews.get(reactTag);
if (view == null) {
throw new IllegalViewOperationException("Trying to send command to a non-existing view " +
"with tag " + reactTag);
}
ViewManager viewManager = resolveViewManager(reactTag);
viewManager.receiveCommand(view, commandId, args);
}
/**
* Show a {@link PopupMenu}.
*

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

@ -748,6 +748,11 @@ public class UIImplementation {
mOperationsQueue.enqueueDispatchCommand(reactTag, commandId, commandArgs);
}
public void dispatchViewManagerCommand(int reactTag, String commandId, @Nullable ReadableArray commandArgs) {
assertViewExists(reactTag, "dispatchViewManagerCommand");
mOperationsQueue.enqueueDispatchCommand(reactTag, commandId, commandArgs);
}
/**
* Show a PopupMenu.
*

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

@ -22,6 +22,7 @@ import com.facebook.debug.holder.PrinterHolder;
import com.facebook.debug.tags.ReactDebugOverlayTags;
import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.Callback;
import com.facebook.react.bridge.Dynamic;
import com.facebook.react.bridge.GuardedRunnable;
import com.facebook.react.bridge.LifecycleEventListener;
import com.facebook.react.bridge.OnBatchCompleteListener;
@ -31,6 +32,7 @@ import com.facebook.react.bridge.ReactMarker;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.bridge.ReadableType;
import com.facebook.react.bridge.UIManager;
import com.facebook.react.bridge.UiThreadUtil;
import com.facebook.react.bridge.WritableMap;
@ -648,11 +650,19 @@ public class UIManagerModule extends ReactContextBaseJavaModule
@ReactMethod
public void dispatchViewManagerCommand(
int reactTag, int commandId, @Nullable ReadableArray commandArgs) {
int reactTag, Dynamic commandId, @Nullable ReadableArray commandArgs) {
// TODO: this is a temporary approach to support ViewManagerCommands in Fabric until
// the dispatchViewManagerCommand() method is supported by Fabric JS API.
UIManagerHelper.getUIManager(getReactApplicationContext(), ViewUtil.getUIManagerType(reactTag))
.dispatchCommand(reactTag, commandId, commandArgs);
if(commandId.getType() == ReadableType.Number) {
final int commandIdNum = commandId.asInt();
UIManagerHelper.getUIManager(getReactApplicationContext(), ViewUtil.getUIManagerType(reactTag))
.dispatchCommand(reactTag, commandIdNum, commandArgs);
} else if (commandId.getType() == ReadableType.String) {
final String commandIdStr = commandId.asString();
UIManagerHelper.getUIManager(getReactApplicationContext(), ViewUtil.getUIManagerType(reactTag))
.dispatchCommand(reactTag, commandIdStr, commandArgs);
}
}
@Override
@ -660,6 +670,11 @@ public class UIManagerModule extends ReactContextBaseJavaModule
mUIImplementation.dispatchViewManagerCommand(reactTag, commandId, commandArgs);
}
@Override
public void dispatchCommand(int reactTag, String commandId, @Nullable ReadableArray commandArgs) {
mUIImplementation.dispatchViewManagerCommand(reactTag, commandId, commandArgs);
}
@ReactMethod
public void playTouchSound() {
AudioManager audioManager =

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

@ -312,6 +312,23 @@ public class UIViewOperationQueue {
}
}
private final class DispatchStringCommandOperation extends ViewOperation {
private final String mCommand;
private final @Nullable ReadableArray mArgs;
public DispatchStringCommandOperation(int tag, String command, @Nullable ReadableArray args) {
super(tag);
mCommand = command;
mArgs = args;
}
@Override
public void execute() {
mNativeViewHierarchyManager.dispatchCommand(mTag, mCommand, mArgs);
}
}
private final class ShowPopupMenuOperation extends ViewOperation {
private final ReadableArray mItems;
@ -659,6 +676,13 @@ public class UIViewOperationQueue {
mOperations.add(new DispatchCommandOperation(reactTag, commandId, commandArgs));
}
public void enqueueDispatchCommand(
int reactTag,
String commandId,
@Nullable ReadableArray commandArgs) {
mOperations.add(new DispatchStringCommandOperation(reactTag, commandId, commandArgs));
}
public void enqueueUpdateExtraData(int reactTag, Object extraData) {
mOperations.add(new UpdateViewExtraData(reactTag, extraData));
}

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

@ -172,6 +172,18 @@ public abstract class ViewManager<T extends View, C extends ReactShadowNode>
public void receiveCommand(@Nonnull T root, int commandId, @Nullable ReadableArray args) {
}
/**
* Subclasses may use this method to receive events/commands directly from JS through the
* {@link UIManager}. Good example of such a command would be {@code scrollTo} request with
* coordinates for a {@link ScrollView} instance.
*
* @param root View instance that should receive the command
* @param commandId code of the command
* @param args optional arguments for the command
*/
public void receiveCommand(@Nonnull T root, String commandId, @Nullable ReadableArray args) {
}
/**
* Subclasses of {@link ViewManager} that expect to receive commands through
* {@link UIManagerModule#dispatchViewManagerCommand} should override this method returning the

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

@ -289,28 +289,54 @@ public class ReactViewManager extends ViewGroupManager<ReactViewGroup> {
public void receiveCommand(ReactViewGroup root, int commandId, @Nullable ReadableArray args) {
switch (commandId) {
case CMD_HOTSPOT_UPDATE: {
if (args == null || args.size() != 2) {
throw new JSApplicationIllegalArgumentException(
"Illegal number of arguments for 'updateHotspot' command");
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
float x = PixelUtil.toPixelFromDIP(args.getDouble(0));
float y = PixelUtil.toPixelFromDIP(args.getDouble(1));
root.drawableHotspotChanged(x, y);
}
handleHotspotUpdate(root, args);
break;
}
case CMD_SET_PRESSED: {
if (args == null || args.size() != 1) {
throw new JSApplicationIllegalArgumentException(
"Illegal number of arguments for 'setPressed' command");
}
root.setPressed(args.getBoolean(0));
handleSetPressed(root, args);
break;
}
}
}
@Override
public void receiveCommand(ReactViewGroup root, String commandId, @Nullable ReadableArray args) {
switch (commandId) {
case "hotspotUpdate": {
handleHotspotUpdate(root, args);
break;
}
case "setPressed": {
handleSetPressed(root, args);
break;
}
}
}
private void handleSetPressed(
ReactViewGroup root,
@Nullable ReadableArray args) {
if (args == null || args.size() != 1) {
throw new JSApplicationIllegalArgumentException(
"Illegal number of arguments for 'setPressed' command");
}
root.setPressed(args.getBoolean(0));
}
private void handleHotspotUpdate(
ReactViewGroup root,
@Nullable ReadableArray args) {
if (args == null || args.size() != 2) {
throw new JSApplicationIllegalArgumentException(
"Illegal number of arguments for 'updateHotspot' command");
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
float x = PixelUtil.toPixelFromDIP(args.getDouble(0));
float y = PixelUtil.toPixelFromDIP(args.getDouble(1));
root.drawableHotspotChanged(x, y);
}
}
@Override
public void addView(ReactViewGroup parent, View child, int index) {
boolean removeClippedSubviews = parent.getRemoveClippedSubviews();