Skip to content

Commit

Permalink
Use ViewManagerDelegate if provided instead of $$PropsSetter to updat…
Browse files Browse the repository at this point in the history
…e view props

Summary:
This diff introduces an interface `ViewManagerDelegate` and its base implementation `BaseViewManagerDelegate`, which is used as a parent class for all view manager delegates generated by the JS codegen. Before the changes in this diff, generated delegates didn't support setting the base view properties such as background color, rotation, opacity, etc. Now it's possible to do by using `BaseViewManagerDelegate.setProperty(...)`, and since all generated delegates extend BaseViewManagerDelegate, they can just call `super.setProperty(...)` for properties they don't want to handle.

This diff also introduced a new method `ViewManager.getDelegate()`. This will allow view managers to return an instance of the delegate generated by JS and ensure that the view properties are set in a type-safe manner. If this method returns null (it does by default), we fall back to the default implementation of setting view properties using Java-generated `$$PropsSetter`
classes.

This is an example of an interface class generated by JS:

```
public interface RCTAxialGradientViewViewManagerInterface<T extends View> {
  void setColors(T view, Nullable ReadableArray value);
  void setLocations(T view, Nullable ReadableArray value);
  void setEndX(T view, Float value);
  void setEndY(T view, Float value);
  void setStartX(T view, Float value);
  void setStartY(T view, Float value);
}
```

This is an example of a delegate class generated by JS:

```
public class RCTAxialGradientViewManagerDelegate<T extends View, U extends BaseViewManager<T, ? extends LayoutShadowNode> & RCTAxialGradientViewManagerInterface<T>> extends BaseViewManagerDelegate<T, U> {
  public RCTAxialGradientViewManagerDelegate(U viewManager) {
    super(viewManager);
  }
  Override
  public void setProperty(T view, String propName, Nullable Object value) {
    switch (propName) {
      case "colors":
        mViewManager.setColors(view, (ReadableArray) value);
        break;
      case "locations":
        mViewManager.setLocations(view, (ReadableArray) value);
        break;
      case "endX":
        mViewManager.setEndX(view, value == null ? Float.NaN : ((Double) value).floatValue());
        break;
      case "endY":
        mViewManager.setEndY(view, value == null ? Float.NaN : ((Double) value).floatValue());
        break;
      case "startX":
        mViewManager.setStartX(view, value == null ? Float.NaN : ((Double) value).floatValue());
        break;
      case "startY":
        mViewManager.setStartY(view, value == null ? Float.NaN : ((Double) value).floatValue());
        break;
      default:
        super.setProperty(view, propName, value);
    }
  }
}
```

NOTE: What if a view manager, for instance ReactAxialGradientManager, wanted to add support for the borderRadius prop? In the old Java codegen, it would just need to create a method and annotate it with ReactProp (name = ViewProps.BORDER_RADIUS) and $$PropsSetter would call this method when a property with this name must be set. With the new JS codegen, borderRadius is a part of the basic view props, so setBorderRadius is not generated as a part of the ViewManagerInterface, so it’s not possible to set this value. I see two options: 1) add a method boolean setProperty (String propName, Object value) and let the view manager handle it in a non-type safe way (return true if it’s been handled). 2) Generate BaseViewManagerInterface which will include all basic view props and make BaseViewManager implement this interface, leaving all methods empty so that it stays compatible with the current implementation. Override these methods in a view manager that needs to handle a specific property in a custom way (so we would override setBorderRadius in ReactAxialGradientManager).

Reviewed By: mdvacca

Differential Revision: D16667686

fbshipit-source-id: 06a15a92f8af55640b7a53c5a34f40366d1be2a8
  • Loading branch information
makovkastar authored and facebook-github-bot committed Aug 12, 2019
1 parent bef87b6 commit 50fe811
Show file tree
Hide file tree
Showing 39 changed files with 611 additions and 254 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -34,35 +34,9 @@
public abstract class BaseViewManager<T extends View, C extends LayoutShadowNode>
extends ViewManager<T, C> {

private static final String PROP_BACKGROUND_COLOR = ViewProps.BACKGROUND_COLOR;
private static final String PROP_TRANSFORM = "transform";
private static final String PROP_ELEVATION = "elevation";
private static final String PROP_Z_INDEX = "zIndex";
private static final String PROP_RENDER_TO_HARDWARE_TEXTURE = "renderToHardwareTextureAndroid";
private static final String PROP_ACCESSIBILITY_LABEL = "accessibilityLabel";
private static final String PROP_ACCESSIBILITY_HINT = "accessibilityHint";
private static final String PROP_ACCESSIBILITY_LIVE_REGION = "accessibilityLiveRegion";
private static final String PROP_ACCESSIBILITY_ROLE = "accessibilityRole";
private static final String PROP_ACCESSIBILITY_STATES = "accessibilityStates";
private static final String PROP_ACCESSIBILITY_STATE = "accessibilityState";
private static final String PROP_ACCESSIBILITY_ACTIONS = "accessibilityActions";
private static final String PROP_IMPORTANT_FOR_ACCESSIBILITY = "importantForAccessibility";

// DEPRECATED
private static final String PROP_ROTATION = "rotation";
private static final String PROP_SCALE_X = "scaleX";
private static final String PROP_SCALE_Y = "scaleY";
private static final String PROP_TRANSLATE_X = "translateX";
private static final String PROP_TRANSLATE_Y = "translateY";

private static final int PERSPECTIVE_ARRAY_INVERTED_CAMERA_DISTANCE_INDEX = 2;
private static final float CAMERA_DISTANCE_NORMALIZATION_MULTIPLIER = (float) Math.sqrt(5);

/** Used to locate views in end-to-end (UI) tests. */
public static final String PROP_TEST_ID = "testID";

public static final String PROP_NATIVE_ID = "nativeID";

private static MatrixMathHelper.MatrixDecompositionContext sMatrixDecompositionContext =
new MatrixMathHelper.MatrixDecompositionContext();
private static double[] sTransformDecompositionArray = new double[16];
Expand All @@ -84,12 +58,15 @@ public abstract class BaseViewManager<T extends View, C extends LayoutShadowNode
private static final String STATE_EXPANDED = "expanded";
private static final String STATE_MIXED = "mixed";

@ReactProp(name = PROP_BACKGROUND_COLOR, defaultInt = Color.TRANSPARENT, customType = "Color")
@ReactProp(
name = ViewProps.BACKGROUND_COLOR,
defaultInt = Color.TRANSPARENT,
customType = "Color")
public void setBackgroundColor(@NonNull T view, int backgroundColor) {
view.setBackgroundColor(backgroundColor);
}

@ReactProp(name = PROP_TRANSFORM)
@ReactProp(name = ViewProps.TRANSFORM)
public void setTransform(@NonNull T view, @Nullable ReadableArray matrix) {
if (matrix == null) {
resetTransformProperty(view);
Expand All @@ -103,12 +80,12 @@ public void setOpacity(@NonNull T view, float opacity) {
view.setAlpha(opacity);
}

@ReactProp(name = PROP_ELEVATION)
@ReactProp(name = ViewProps.ELEVATION)
public void setElevation(@NonNull T view, float elevation) {
ViewCompat.setElevation(view, PixelUtil.toPixelFromDIP(elevation));
}

@ReactProp(name = PROP_Z_INDEX)
@ReactProp(name = ViewProps.Z_INDEX)
public void setZIndex(@NonNull T view, float zIndex) {
int integerZIndex = Math.round(zIndex);
ViewGroupManager.setViewZIndex(view, integerZIndex);
Expand All @@ -118,46 +95,46 @@ public void setZIndex(@NonNull T view, float zIndex) {
}
}

@ReactProp(name = PROP_RENDER_TO_HARDWARE_TEXTURE)
@ReactProp(name = ViewProps.RENDER_TO_HARDWARE_TEXTURE)
public void setRenderToHardwareTexture(@NonNull T view, boolean useHWTexture) {
view.setLayerType(useHWTexture ? View.LAYER_TYPE_HARDWARE : View.LAYER_TYPE_NONE, null);
}

@ReactProp(name = PROP_TEST_ID)
@ReactProp(name = ViewProps.TEST_ID)
public void setTestId(@NonNull T view, String testId) {
view.setTag(R.id.react_test_id, testId);

// temporarily set the tag and keyed tags to avoid end to end test regressions
view.setTag(testId);
}

@ReactProp(name = PROP_NATIVE_ID)
@ReactProp(name = ViewProps.NATIVE_ID)
public void setNativeId(@NonNull T view, String nativeId) {
view.setTag(R.id.view_tag_native_id, nativeId);
ReactFindViewUtil.notifyViewRendered(view);
}

@ReactProp(name = PROP_ACCESSIBILITY_LABEL)
@ReactProp(name = ViewProps.ACCESSIBILITY_LABEL)
public void setAccessibilityLabel(@NonNull T view, String accessibilityLabel) {
view.setTag(R.id.accessibility_label, accessibilityLabel);
updateViewContentDescription(view);
}

@ReactProp(name = PROP_ACCESSIBILITY_HINT)
@ReactProp(name = ViewProps.ACCESSIBILITY_HINT)
public void setAccessibilityHint(@NonNull T view, String accessibilityHint) {
view.setTag(R.id.accessibility_hint, accessibilityHint);
updateViewContentDescription(view);
}

@ReactProp(name = PROP_ACCESSIBILITY_ROLE)
@ReactProp(name = ViewProps.ACCESSIBILITY_ROLE)
public void setAccessibilityRole(@NonNull T view, @Nullable String accessibilityRole) {
if (accessibilityRole == null) {
return;
}
view.setTag(R.id.accessibility_role, AccessibilityRole.fromValue(accessibilityRole));
}

@ReactProp(name = PROP_ACCESSIBILITY_STATES)
@ReactProp(name = ViewProps.ACCESSIBILITY_STATES)
public void setViewStates(@NonNull T view, @Nullable ReadableArray accessibilityStates) {
boolean shouldUpdateContentDescription =
view.getTag(R.id.accessibility_states) != null && accessibilityStates == null;
Expand All @@ -182,7 +159,7 @@ public void setViewStates(@NonNull T view, @Nullable ReadableArray accessibility
}
}

@ReactProp(name = PROP_ACCESSIBILITY_STATE)
@ReactProp(name = ViewProps.ACCESSIBILITY_STATE)
public void setViewState(@NonNull T view, @Nullable ReadableMap accessibilityState) {
if (accessibilityState == null) {
return;
Expand Down Expand Up @@ -257,7 +234,7 @@ private void updateViewContentDescription(@NonNull T view) {
}
}

@ReactProp(name = PROP_ACCESSIBILITY_ACTIONS)
@ReactProp(name = ViewProps.ACCESSIBILITY_ACTIONS)
public void setAccessibilityActions(T view, ReadableArray accessibilityActions) {
if (accessibilityActions == null) {
return;
Expand All @@ -266,7 +243,7 @@ public void setAccessibilityActions(T view, ReadableArray accessibilityActions)
view.setTag(R.id.accessibility_actions, accessibilityActions);
}

@ReactProp(name = PROP_IMPORTANT_FOR_ACCESSIBILITY)
@ReactProp(name = ViewProps.IMPORTANT_FOR_ACCESSIBILITY)
public void setImportantForAccessibility(
@NonNull T view, @Nullable String importantForAccessibility) {
if (importantForAccessibility == null || importantForAccessibility.equals("auto")) {
Expand All @@ -282,36 +259,36 @@ public void setImportantForAccessibility(
}

@Deprecated
@ReactProp(name = PROP_ROTATION)
@ReactProp(name = ViewProps.ROTATION)
public void setRotation(@NonNull T view, float rotation) {
view.setRotation(rotation);
}

@Deprecated
@ReactProp(name = PROP_SCALE_X, defaultFloat = 1f)
@ReactProp(name = ViewProps.SCALE_X, defaultFloat = 1f)
public void setScaleX(@NonNull T view, float scaleX) {
view.setScaleX(scaleX);
}

@Deprecated
@ReactProp(name = PROP_SCALE_Y, defaultFloat = 1f)
@ReactProp(name = ViewProps.SCALE_Y, defaultFloat = 1f)
public void setScaleY(@NonNull T view, float scaleY) {
view.setScaleY(scaleY);
}

@Deprecated
@ReactProp(name = PROP_TRANSLATE_X, defaultFloat = 0f)
@ReactProp(name = ViewProps.TRANSLATE_X, defaultFloat = 0f)
public void setTranslateX(@NonNull T view, float translateX) {
view.setTranslationX(PixelUtil.toPixelFromDIP(translateX));
}

@Deprecated
@ReactProp(name = PROP_TRANSLATE_Y, defaultFloat = 0f)
@ReactProp(name = ViewProps.TRANSLATE_Y, defaultFloat = 0f)
public void setTranslateY(@NonNull T view, float translateY) {
view.setTranslationY(PixelUtil.toPixelFromDIP(translateY));
}

@ReactProp(name = PROP_ACCESSIBILITY_LIVE_REGION)
@ReactProp(name = ViewProps.ACCESSIBILITY_LIVE_REGION)
public void setAccessibilityLiveRegion(@NonNull T view, @Nullable String liveRegion) {
if (liveRegion == null || liveRegion.equals("none")) {
ViewCompat.setAccessibilityLiveRegion(view, ViewCompat.ACCESSIBILITY_LIVE_REGION_NONE);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
package com.facebook.react.uimanager;

import android.view.View;
import androidx.annotation.Nullable;
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.bridge.ReadableMap;

/**
* This is a base implementation of {@link ViewManagerDelegate} which supports setting properties
* that every view should support, such as rotation, background color, etc.
*/
public abstract class BaseViewManagerDelegate<
T extends View, U extends BaseViewManager<T, ? extends LayoutShadowNode>>
implements ViewManagerDelegate<T> {
protected final U mViewManager;

public BaseViewManagerDelegate(U viewManager) {
mViewManager = viewManager;
}

@Override
public void setProperty(T view, String propName, @Nullable Object value) {
switch (propName) {
case ViewProps.ACCESSIBILITY_ACTIONS:
mViewManager.setAccessibilityActions(view, (ReadableArray) value);
break;
case ViewProps.ACCESSIBILITY_HINT:
mViewManager.setAccessibilityHint(view, (String) value);
break;
case ViewProps.ACCESSIBILITY_LABEL:
mViewManager.setAccessibilityLabel(view, (String) value);
break;
case ViewProps.ACCESSIBILITY_LIVE_REGION:
mViewManager.setAccessibilityLiveRegion(view, (String) value);
break;
case ViewProps.ACCESSIBILITY_ROLE:
mViewManager.setAccessibilityRole(view, (String) value);
break;
case ViewProps.ACCESSIBILITY_STATE:
mViewManager.setViewState(view, (ReadableMap) value);
break;
case ViewProps.ACCESSIBILITY_STATES:
mViewManager.setViewStates(view, (ReadableArray) value);
break;
case ViewProps.BACKGROUND_COLOR:
mViewManager.setBackgroundColor(view, value == null ? 0 : ((Double) value).intValue());
break;
case ViewProps.ELEVATION:
mViewManager.setElevation(view, value == null ? 0.0f : ((Double) value).floatValue());
break;
case ViewProps.IMPORTANT_FOR_ACCESSIBILITY:
mViewManager.setImportantForAccessibility(view, (String) value);
break;
case ViewProps.NATIVE_ID:
mViewManager.setNativeId(view, (String) value);
break;
case ViewProps.OPACITY:
mViewManager.setOpacity(view, value == null ? 1.0f : ((Double) value).floatValue());
break;
case ViewProps.RENDER_TO_HARDWARE_TEXTURE:
//noinspection SimplifiableConditionalExpression
mViewManager.setRenderToHardwareTexture(view, value == null ? false : (boolean) value);
break;
case ViewProps.ROTATION:
mViewManager.setRotation(view, value == null ? 0.0f : ((Double) value).floatValue());
break;
case ViewProps.SCALE_X:
mViewManager.setScaleX(view, value == null ? 1.0f : ((Double) value).floatValue());
break;
case ViewProps.SCALE_Y:
mViewManager.setScaleY(view, value == null ? 1.0f : ((Double) value).floatValue());
break;
case ViewProps.TEST_ID:
mViewManager.setTestId(view, (String) value);
break;
case ViewProps.TRANSFORM:
mViewManager.setTransform(view, (ReadableArray) value);
break;
case ViewProps.TRANSLATE_X:
mViewManager.setTranslateX(view, value == null ? 0.0f : ((Double) value).floatValue());
break;
case ViewProps.TRANSLATE_Y:
mViewManager.setTranslateY(view, value == null ? 0.0f : ((Double) value).floatValue());
break;
case ViewProps.Z_INDEX:
mViewManager.setZIndex(view, value == null ? 0.0f : ((Double) value).floatValue());
break;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -40,10 +40,32 @@ public abstract class ViewManager<T extends View, C extends ReactShadowNode>
* @param stateWrapper
*/
public void updateProperties(@NonNull T viewToUpdate, ReactStylesDiffMap props) {
ViewManagerPropertyUpdater.updateProps(this, viewToUpdate, props);
final ViewManagerDelegate<T> delegate = getDelegate();
if (delegate != null) {
ViewManagerPropertyUpdater.updateProps(delegate, viewToUpdate, props);
} else {
ViewManagerPropertyUpdater.updateProps(this, viewToUpdate, props);
}
onAfterUpdateTransaction(viewToUpdate);
}

/**
* Override this method and return an instance of {@link ViewManagerDelegate} if the props of the
* view managed by this view manager should be set via this delegate. The provided instance will
* then get calls to {@link ViewManagerDelegate#setProperty(View, String, Object)} for every prop
* that must be updated and it's the delegate's responsibility to apply these values to the view.
*
* <p>By default this method returns {@code null}, which means that the view manager doesn't have
* a delegate and the view props should be set internally by the view manager itself.
*
* @return an instance of {@link ViewManagerDelegate} if the props of the view managed by this
* view manager should be set via this delegate
*/
@Nullable
protected ViewManagerDelegate<T> getDelegate() {
return null;
}

/** Creates a view and installs event emitters on it. */
private final @NonNull T createView(
@NonNull ThemedReactContext reactContext, JSResponderHandler jsResponderHandler) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.facebook.react.uimanager;

import android.view.View;
import androidx.annotation.Nullable;

/**
* This is an interface that must be implemented by classes that wish to take over the
* responsibility of setting properties of all views managed by the view manager.
*
* @param <T> the type of the view supported by this delegate
*/
public interface ViewManagerDelegate<T extends View> {
void setProperty(T view, String propName, @Nullable Object value);
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,15 @@ public static void clear() {
SHADOW_NODE_SETTER_MAP.clear();
}

public static <T extends ViewManagerDelegate<V>, V extends View> void updateProps(
T delegate, V v, ReactStylesDiffMap props) {
Iterator<Map.Entry<String, Object>> iterator = props.mBackingMap.getEntryIterator();
while (iterator.hasNext()) {
Map.Entry<String, Object> entry = iterator.next();
delegate.setProperty(v, entry.getKey(), entry.getValue());
}
}

public static <T extends ViewManager, V extends View> void updateProps(
T manager, V v, ReactStylesDiffMap props) {
ViewManagerSetter<T, V> setter = findManagerSetter(manager.getClass());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,31 @@ public class ViewProps {
public static final String BORDER_END_COLOR = "borderEndColor";
public static final String ON_LAYOUT = "onLayout";

public static final String TRANSFORM = "transform";
public static final String ELEVATION = "elevation";
public static final String Z_INDEX = "zIndex";
public static final String RENDER_TO_HARDWARE_TEXTURE = "renderToHardwareTextureAndroid";
public static final String ACCESSIBILITY_LABEL = "accessibilityLabel";
public static final String ACCESSIBILITY_HINT = "accessibilityHint";
public static final String ACCESSIBILITY_LIVE_REGION = "accessibilityLiveRegion";
public static final String ACCESSIBILITY_ROLE = "accessibilityRole";
public static final String ACCESSIBILITY_STATES = "accessibilityStates";
public static final String ACCESSIBILITY_STATE = "accessibilityState";
public static final String ACCESSIBILITY_ACTIONS = "accessibilityActions";
public static final String IMPORTANT_FOR_ACCESSIBILITY = "importantForAccessibility";

// DEPRECATED
public static final String ROTATION = "rotation";
public static final String SCALE_X = "scaleX";
public static final String SCALE_Y = "scaleY";
public static final String TRANSLATE_X = "translateX";
public static final String TRANSLATE_Y = "translateY";

/** Used to locate views in end-to-end (UI) tests. */
public static final String TEST_ID = "testID";

public static final String NATIVE_ID = "nativeID";

public static final int[] BORDER_SPACING_TYPES = {
Spacing.ALL,
Spacing.START,
Expand Down
1 change: 1 addition & 0 deletions packages/react-native-codegen/DEFS.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -270,6 +270,7 @@ def rn_codegen(
deps = [
react_native_dep("third-party/android/androidx:annotation"),
react_native_target("java/com/facebook/react/bridge:bridge"),
react_native_target("java/com/facebook/react/uimanager:uimanager"),
],
)

Expand Down
Loading

0 comments on commit 50fe811

Please sign in to comment.