Introducing view property annotation.

Differential Revision: D2475680

committer: Service User <svcscm@fb.com>
This commit is contained in:
Krzysztof Magiera 2015-09-24 04:30:29 -07:00 committed by facebook-github-bot-9
parent 6c3fb77f30
commit 5623c831b3
8 changed files with 634 additions and 19 deletions

View File

@ -17,4 +17,8 @@ public class JSApplicationIllegalArgumentException extends JSApplicationCausedNa
public JSApplicationIllegalArgumentException(String detailMessage) {
super(detailMessage);
}
public JSApplicationIllegalArgumentException(String detailMessage, Throwable t) {
super(detailMessage, t);
}
}

View File

@ -93,7 +93,7 @@ import com.facebook.react.uimanager.events.EventDispatcher;
throw new IllegalViewOperationException("Trying to update view with tag " + tag
+ " which doesn't exist");
}
viewManager.updateView(viewToUpdate, props);
viewManager.updateProperties(viewToUpdate, props);
}
public void updateViewExtraData(int tag, Object extraData) {
@ -179,7 +179,7 @@ import com.facebook.react.uimanager.events.EventDispatcher;
// creating another (potentially much more expensive) mapping from view to React tag
view.setId(tag);
if (initialProps != null) {
viewManager.updateView(view, initialProps);
viewManager.updateProperties(view, initialProps);
}
}

View File

@ -0,0 +1,93 @@
// Copyright 2004-present Facebook. All Rights Reserved.
package com.facebook.react.uimanager;
import javax.annotation.Nullable;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
/**
* Use this annotation to annotate properties of native views that should be exposed to JS. This
* annotation should only be used for setter methods of subclasses of {@link ViewManager}.
*
* Each annotated method should return {@code void} and take exactly two arguments: first being
* a view instance to be updated and second a value that should be set.
*
* Allowed types of values are:
* - primitives (int, boolean, double, float)
* - {@link String}
* - {@link Boolean}
* - {@link ReadableArray}
* - {@link ReadableMap}
*
* When property gets removed from the corresponding component in React, annotated setter will be
* called with {@code null} in case of non-primitive value type or with a default value in case when
* the value type is a primitive (use appropriate default field of this annotation to customize
* default value that is going to be used: {@link #defaultBoolean}, {@link #defaultDouble}, etc.)
*
* Since in case of property removal for non-primitive value type setter will be called with value
* set to {@code null} it's required that value type is annotated with {@link Nullable}.
*
* Note: Since boolean property type can be represented both as primitive and wrapped default value
* set through {@link #defaultBoolean} is only respected for primitive type and for the wrapped type
* {@code null} will be used as a default.
*/
@Retention(RUNTIME)
@Target(ElementType.METHOD)
public @interface ReactProp {
// Used as a default value for "customType" property as "null" is not allowed. Moreover, when this
// const is used in annotation declaration compiler will actually create a copy of it, so
// comparing it using "==" with this filed doesn't work either. We need to compare using "equals"
// which means that this value needs to be unique.
String USE_DEFAULT_TYPE = "__default_type__";
/**
* Name of the property exposed to JS that will be updated using setter method annotated with
* the given instance of {@code ReactProp} annotation
*/
String name();
/**
* Type of property that will be send to JS. In most of the cases {@code customType} should not be
* set in which case default type will be send to JS based on the type of value argument from the
* setter method (e.g. for {@code int}, {@code double} default is "number", for
* {@code ReadableArray} it's "Array"). Custom type may be used when additional processing of the
* value needs to be done in JS before sending it over the brige. A good example of that would be
* backgroundColor property, which is expressed as a {@code String} in JS, but we use
* {@code processColor} JS module to convert it to {@code int} before sending over the bridge.
*/
@Nullable String customType() default USE_DEFAULT_TYPE;
/**
* Default value for property of type {@code double}. This value will be provided to property
* setter method annotated with {@link ReactProp} if property with a given name gets removed
* from the component description in JS
*/
double defaultDouble() default 0.0;
/**
* Default value for property of type {@code float}. This value will be provided to property
* setter method annotated with {@link ReactProp} if property with a given name gets removed
* from the component description in JS
*/
float defaultFloat() default 0.0f;
/**
* Default value for property of type {@code int}. This value will be provided to property
* setter method annotated with {@link ReactProp} if property with a given name gets removed
* from the component description in JS
*/
int defaultInt() default 0;
/**
* Default value for property of type {@code boolean}. This value will be provided to property
* setter method annotated with {@link ReactProp} if property with a given name gets removed
* from the component description in JS
*/
boolean defaultBoolean() default false;
}

View File

@ -0,0 +1,77 @@
// Copyright 2004-present Facebook. All Rights Reserved.
package com.facebook.react.uimanager;
import javax.annotation.Nullable;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
/**
* Use this annotation to annotate group of properties of native views that should be exposed to JS.
* This annotation should only be used for setter methods of subclasses of {@link ViewManager}. It's
* a batched version of {@link ReactProp} annotation (please see documentation of {@link ReactProp}
* for more details about how this annotation can be used).
*
* This annotation is meant to be used for a group of similar properties. That's why it support only
* a set of properties of the same type. A good example is supporting "border", where we have 7
* variations of that property ("borderLeft", "borderHorizontal", etc.) and very similar code for
* handling each of those.
*
* Each annotated method should return {@code void} and take exactly three arguments: first being
* a view instance to be updated, second should be of type int and will represent index in the
* group of the property being updated. Last, third argument represent the value that should be set.
*
*
* Currently only {@code int}, {@code float} and {@link String} value types are supported.
*
* In case when property has been removed from the corresponding react component annotated setter
* will be called and default value will be provided as a value parameter. Default value can be
* customize using {@link #defaultInt} or {@link #defaultFloat} in the case when property is of
* one of primitive types. In case when {@link String} is the property type {@code null} value will
* be provided as a default.
*/
@Retention(RUNTIME)
@Target(ElementType.METHOD)
public @interface ReactPropGroup {
// Used as a default value for "customType" property as "null" is not allowed. Moreover, when this
// const is used in annotation declaration compiler will actually create a copy of it, so
// comparing it using "==" with this filed doesn't work either. We need to compare using "equals"
// which means that this value needs to be unique.
String USE_DEFAULT_TYPE = "__default_type__";
/**
* Array of names of properties exposed to JS that will be updated using setter method annotated
* with the given instance of {@code ReactPropGroup} annotation
*/
String[] names();
/**
* Type of property that will be send to JS. In most of the cases {@code customType} should not be
* set in which case default type will be send to JS based on the type of value argument from the
* setter method (e.g. for {@code int}, {@code float} default is "number"). Custom type may be
* used when additional processing of the value needs to be done in JS before sending it over the
* bridge. A good example of that would be backgroundColor property, which is expressed as a
* {@code String} in JS, but we use {@code processColor} JS module to convert it to {@code int}
* before sending over the bridge.
*/
@Nullable String customType() default USE_DEFAULT_TYPE;
/**
* Default value for property of type {@code float}. This value will be provided to property
* setter method annotated with {@link ReactPropGroup} if property with a given name gets removed
* from the component description in JS
*/
float defaultFloat() default 0.0f;
/**
* Default value for property of type {@code int}. This value will be provided to property
* setter method annotated with {@link ReactPropGroup} if property with a given name gets removed
* from the component description in JS
*/
int defaultInt() default 0;
}

View File

@ -64,13 +64,9 @@ import com.facebook.react.common.MapBuilder;
if (viewManagerCommands != null) {
viewManagerConstants.put("Commands", viewManagerCommands);
}
Map<String, UIProp.Type> viewManagerNativeProps = viewManager.getNativeProps();
Map<String, String> viewManagerNativeProps = viewManager.getNativeProps();
if (!viewManagerNativeProps.isEmpty()) {
Map<String, String> nativeProps = new HashMap<>();
for (Map.Entry<String, UIProp.Type> entry : viewManagerNativeProps.entrySet()) {
nativeProps.put(entry.getKey(), entry.getValue().toString());
}
viewManagerConstants.put("NativeProps", nativeProps);
viewManagerConstants.put("NativeProps", viewManagerNativeProps);
}
if (!viewManagerConstants.isEmpty()) {
constants.put(viewManager.getName(), viewManagerConstants);

View File

@ -18,10 +18,11 @@ import java.util.Map;
import android.view.View;
import com.facebook.csslayout.CSSNode;
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.bridge.ReadableMapKeySeyIterator;
import com.facebook.react.touch.CatalystInterceptingViewGroup;
import com.facebook.react.touch.JSResponderHandler;
import com.facebook.react.bridge.ReactContext;
import com.facebook.react.bridge.ReadableArray;
/**
* Class responsible for knowing how to create and update catalyst Views of a given type. It is also
@ -32,6 +33,28 @@ public abstract class ViewManager<T extends View, C extends ReactShadowNode> {
private static final Map<Class, Map<String, UIProp.Type>> CLASS_PROP_CACHE = new HashMap<>();
public final void updateProperties(T viewToUpdate, CatalystStylesDiffMap props) {
Map<String, ViewManagersPropertyCache.PropSetter> propSetters =
ViewManagersPropertyCache.getNativePropSettersForClass(getClass());
ReadableMap propMap = props.mBackingMap;
ReadableMapKeySeyIterator iterator = propMap.keySetIterator();
// TODO(krzysztof): Remove missingSetters code once all views are migrated to @ReactProp
boolean missingSetters = false;
while (iterator.hasNextKey()) {
String key = iterator.nextKey();
ViewManagersPropertyCache.PropSetter setter = propSetters.get(key);
if (setter != null) {
setter.updateProp(this, viewToUpdate, props);
} else {
missingSetters = true;
}
}
if (missingSetters) {
updateView(viewToUpdate, props);
}
onAfterUpdateTransaction(viewToUpdate);
}
/**
* Creates a view and installs event emitters on it.
*/
@ -84,8 +107,22 @@ public abstract class ViewManager<T extends View, C extends ReactShadowNode> {
* Subclass should use this method to populate native view with updated style properties. In case
* when a certain property is present in {@param props} map but the value is null, this property
* should be reset to the default value
*
* TODO(krzysztof) This method should be replaced by updateProperties and removed completely after
* all view managers adapt @ReactProp
*/
public abstract void updateView(T root, CatalystStylesDiffMap props);
@Deprecated
protected void updateView(T root, CatalystStylesDiffMap props) {
}
/**
* Callback that will be triggered after all properties are updated in current update transaction
* (all @ReactProp handlers for properties updated in current transaction have been called). If
* you want to override this method you should call super.onAfterUpdateTransaction from it as
* the parent class of the ViewManager may rely on callback being executed.
*/
protected void onAfterUpdateTransaction(T view) {
}
/**
* Subclasses can implement this method to receive an optional extra data enqueued from the
@ -178,13 +215,15 @@ public abstract class ViewManager<T extends View, C extends ReactShadowNode> {
return null;
}
public Map<String, UIProp.Type> getNativeProps() {
Map<String, UIProp.Type> nativeProps = new HashMap<>();
public Map<String, String> getNativeProps() {
// TODO(krzysztof): This method will just delegate to ViewManagersPropertyRegistry once
// refactoring is finished
Class cls = getClass();
Map<String, String> nativeProps = ViewManagersPropertyCache.getNativePropsForClass(cls);
while (cls.getSuperclass() != null) {
Map<String, UIProp.Type> props = getNativePropsForClass(cls);
for (Map.Entry<String, UIProp.Type> entry : props.entrySet()) {
nativeProps.put(entry.getKey(), entry.getValue());
nativeProps.put(entry.getKey(), entry.getValue().toString());
}
cls = cls.getSuperclass();
}
@ -192,6 +231,7 @@ public abstract class ViewManager<T extends View, C extends ReactShadowNode> {
}
private Map<String, UIProp.Type> getNativePropsForClass(Class cls) {
// TODO(krzysztof): Blow up this method once refactoring is finished
Map<String, UIProp.Type> props = CLASS_PROP_CACHE.get(cls);
if (props != null) {
return props;

View File

@ -0,0 +1,405 @@
// Copyright 2004-present Facebook. All Rights Reserved.
package com.facebook.react.uimanager;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import android.view.View;
import com.facebook.common.logging.FLog;
import com.facebook.react.bridge.JSApplicationIllegalArgumentException;
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.bridge.ReadableMap;
/**
* This class is responsible for holding view manager property setters and is used in a process of
* updating views with the new properties set in JS.
*/
/*package*/ class ViewManagersPropertyCache {
private static final Map<Class, Map<String, PropSetter>> CLASS_PROPS_CACHE = new HashMap<>();
private static final Map<String, PropSetter> EMPTY_PROPS_MAP = new HashMap<>();
/*package*/ static abstract class PropSetter {
protected final String mPropName;
protected final String mPropType;
protected final Method mSetter;
// The following two constructors make it easy to reuse code responsible for setting property
// type. It's probably not a best design but this API is not exposed and since we can't use
// inheritance for annotation classes it's the easiest way to avoid creating an extra base class
// just to support group and non-group setters.
private PropSetter(ReactProp prop, String defaultType, Method setter) {
mPropName = prop.name();
mPropType = ReactProp.USE_DEFAULT_TYPE.equals(prop.customType()) ?
defaultType : prop.customType();
mSetter = setter;
}
private PropSetter(ReactPropGroup prop, String defaultType, Method setter, int index) {
mPropName = prop.names()[index];
mPropType = ReactPropGroup.USE_DEFAULT_TYPE.equals(prop.customType()) ?
defaultType : prop.customType();
mSetter = setter;
}
public String getPropName() {
return mPropName;
}
public String getPropType() {
return mPropType;
}
public void updateProp(
ViewManager viewManager,
View viewToUpdate,
CatalystStylesDiffMap props) {
try {
updateProperty(viewManager, viewToUpdate, props);
} catch (Throwable t) {
FLog.e(ViewManager.class, "Error while updating prop " + mPropName, t);
throw new JSApplicationIllegalArgumentException("Error while updating property '" +
mPropName + "' of a view managed by: " + viewManager.getName(), t);
}
}
protected abstract void updateProperty(
ViewManager viewManager,
View viewToUpdate,
CatalystStylesDiffMap props) throws InvocationTargetException, IllegalAccessException;
}
private static class IntPropSetter extends PropSetter {
private final int mDefaultValue;
public IntPropSetter(ReactProp prop, Method setter, int defaultValue) {
super(prop, "number", setter);
mDefaultValue = defaultValue;
}
@Override
protected void updateProperty(
ViewManager viewManager,
View viewToUpdate,
CatalystStylesDiffMap props) throws InvocationTargetException, IllegalAccessException {
mSetter.invoke(viewManager, viewToUpdate, props.getInt(mPropName, mDefaultValue));
}
}
private static class DoublePropSetter extends PropSetter {
private final double mDefaultValue;
public DoublePropSetter(ReactProp prop, Method setter, double defaultValue) {
super(prop, "number", setter);
mDefaultValue = defaultValue;
}
@Override
protected void updateProperty(
ViewManager viewManager,
View viewToUpdate,
CatalystStylesDiffMap props) throws InvocationTargetException, IllegalAccessException {
mSetter.invoke(viewManager, viewToUpdate, props.getDouble(mPropName, mDefaultValue));
}
}
private static class BooleanPropSetter extends PropSetter {
private final boolean mDefaultValue;
public BooleanPropSetter(ReactProp prop, Method setter, boolean defaultValue) {
super(prop, "boolean", setter);
mDefaultValue = defaultValue;
}
@Override
protected void updateProperty(
ViewManager viewManager,
View viewToUpdate,
CatalystStylesDiffMap props) throws InvocationTargetException, IllegalAccessException {
mSetter.invoke(viewManager, viewToUpdate, props.getBoolean(mPropName, mDefaultValue));
}
}
private static class FloatPropSetter extends PropSetter {
private final float mDefaultValue;
public FloatPropSetter(ReactProp prop, Method setter, float defaultValue) {
super(prop, "number", setter);
mDefaultValue = defaultValue;
}
@Override
protected void updateProperty(
ViewManager viewManager,
View viewToUpdate,
CatalystStylesDiffMap props) throws InvocationTargetException, IllegalAccessException {
mSetter.invoke(viewManager, viewToUpdate, props.getFloat(mPropName, mDefaultValue));
}
}
private static class ArrayPropSetter extends PropSetter {
public ArrayPropSetter(ReactProp prop, Method setter) {
super(prop, "Array", setter);
}
@Override
protected void updateProperty(
ViewManager viewManager,
View viewToUpdate,
CatalystStylesDiffMap props) throws InvocationTargetException, IllegalAccessException {
mSetter.invoke(viewManager, viewToUpdate, props.getArray(mPropName));
}
}
private static class MapPropSetter extends PropSetter {
public MapPropSetter(ReactProp prop, Method setter) {
super(prop, "Map", setter);
}
@Override
protected void updateProperty(
ViewManager viewManager,
View viewToUpdate,
CatalystStylesDiffMap props) throws InvocationTargetException, IllegalAccessException {
mSetter.invoke(viewManager, viewToUpdate, props.getMap(mPropName));
}
}
private static class StringPropSetter extends PropSetter {
public StringPropSetter(ReactProp prop, Method setter) {
super(prop, "String", setter);
}
@Override
protected void updateProperty(
ViewManager viewManager,
View viewToUpdate,
CatalystStylesDiffMap props) throws InvocationTargetException, IllegalAccessException {
mSetter.invoke(viewManager, viewToUpdate, props.getString(mPropName));
}
}
private static class BoxedBooleanPropSetter extends PropSetter {
public BoxedBooleanPropSetter(ReactProp prop, Method setter) {
super(prop, "boolean", setter);
}
@Override
protected void updateProperty(
ViewManager viewManager,
View viewToUpdate,
CatalystStylesDiffMap props) throws InvocationTargetException, IllegalAccessException {
Boolean value = null;
if (!props.isNull(mPropName)) {
value = props.getBoolean(mPropName, false) ? Boolean.TRUE : Boolean.FALSE;
}
mSetter.invoke(viewManager, viewToUpdate, value);
}
}
private static class BoxedIntPropSetter extends PropSetter {
public BoxedIntPropSetter(ReactProp prop, Method setter) {
super(prop, "number", setter);
}
@Override
protected void updateProperty(
ViewManager viewManager,
View viewToUpdate,
CatalystStylesDiffMap props) throws InvocationTargetException, IllegalAccessException {
Integer value = null;
if (!props.isNull(mPropName)) {
value = props.getInt(mPropName, /* ignored */ 0);
}
mSetter.invoke(viewManager, viewToUpdate, value);
}
}
private static abstract class GroupSetter extends PropSetter {
protected final int mIndex;
protected GroupSetter(ReactPropGroup prop, String defaultType, Method setter, int index) {
super(prop, defaultType, setter, index);
mIndex = index;
}
}
private static class GroupIntSetter extends GroupSetter {
private final int mDefaultValue;
public GroupIntSetter(ReactPropGroup prop, Method setter, int index, int defaultValue) {
super(prop, "number", setter, index);
mDefaultValue = defaultValue;
}
@Override
protected void updateProperty(
ViewManager viewManager,
View viewToUpdate,
CatalystStylesDiffMap props) throws InvocationTargetException, IllegalAccessException {
mSetter.invoke(viewManager, viewToUpdate, mIndex, props.getInt(mPropName, mDefaultValue));
}
}
private static class GroupFloatSetter extends GroupSetter {
private final float mDefaultValue;
public GroupFloatSetter(ReactPropGroup prop, Method setter, int index, float defaultValue) {
super(prop, "number", setter, index);
mDefaultValue = defaultValue;
}
@Override
protected void updateProperty(
ViewManager viewManager,
View viewToUpdate,
CatalystStylesDiffMap props) throws InvocationTargetException, IllegalAccessException {
mSetter.invoke(viewManager, viewToUpdate, mIndex, props.getFloat(mPropName, mDefaultValue));
}
}
private static class GroupBoxedIntSetter extends GroupSetter {
protected GroupBoxedIntSetter(ReactPropGroup prop, Method setter, int index) {
super(prop, "number", setter, index);
}
@Override
protected void updateProperty(
ViewManager viewManager,
View viewToUpdate,
CatalystStylesDiffMap props) throws InvocationTargetException, IllegalAccessException {
mSetter.invoke(
viewManager,
viewToUpdate,
mIndex,
props.isNull(mPropName) ? null : props.getInt(mPropName, /* unused */ 0));
}
}
/*package*/ static Map<String, String> getNativePropsForClass(
Class<? extends ViewManager> topLevelClass) {
Map<String, String> nativeProps = new HashMap<>();
Map<String, PropSetter> props = getNativePropSettersForClass(topLevelClass);
for (PropSetter setter : props.values()) {
nativeProps.put(setter.getPropName(), setter.getPropType());
}
return nativeProps;
}
/*package*/ static Map<String, PropSetter> getNativePropSettersForClass(
Class<? extends ViewManager> cls) {
if (cls == ViewManager.class) {
return EMPTY_PROPS_MAP;
}
Map<String, PropSetter> props = CLASS_PROPS_CACHE.get(cls);
if (props != null) {
return props;
}
props = new HashMap<>(
getNativePropSettersForClass((Class<? extends ViewManager>) cls.getSuperclass()));
for (Method method : cls.getDeclaredMethods()) {
{
ReactProp annotation = method.getAnnotation(ReactProp.class);
if (annotation != null) {
Class<?>[] paramTypes = method.getParameterTypes();
if (paramTypes.length != 2) {
throw new RuntimeException("Wrong number of args for prop setter: " +
cls.getName() + "#" + method.getName());
}
if (!View.class.isAssignableFrom(paramTypes[0])) {
throw new RuntimeException("First param should be a view subclass to be updated: " +
cls.getName() + "#" + method.getName());
}
Class<?> propTypeClass = paramTypes[1];
PropSetter propSetter;
if (propTypeClass == boolean.class) {
propSetter =
new BooleanPropSetter(annotation, method, annotation.defaultBoolean());
} else if (propTypeClass == int.class) {
propSetter = new IntPropSetter(annotation, method, annotation.defaultInt());
} else if (propTypeClass == float.class) {
propSetter = new FloatPropSetter(annotation, method, annotation.defaultFloat());
} else if (propTypeClass == double.class) {
propSetter =
new DoublePropSetter(annotation, method, annotation.defaultDouble());
} else if (propTypeClass == String.class) {
propSetter = new StringPropSetter(annotation, method);
} else if (propTypeClass == Boolean.class) {
propSetter = new BoxedBooleanPropSetter(annotation, method);
} else if (propTypeClass == Integer.class) {
propSetter = new BoxedIntPropSetter(annotation, method);
} else if (propTypeClass == ReadableArray.class) {
propSetter = new ArrayPropSetter(annotation, method);
} else if (propTypeClass == ReadableMap.class) {
propSetter = new MapPropSetter(annotation, method);
} else {
throw new RuntimeException("Unrecognized type");
}
props.put(annotation.name(), propSetter);
}
}
{
ReactPropGroup annotation = method.getAnnotation(ReactPropGroup.class);
if (annotation != null) {
Class<?> [] paramTypes = method.getParameterTypes();
if (paramTypes.length != 3) {
throw new RuntimeException("Wrong number of args for group prop setter: " +
cls.getName() + "#" + method.getName());
}
if (!View.class.isAssignableFrom(paramTypes[0])) {
throw new RuntimeException("First param should be a view subclass to be updated: " +
cls.getName() + "#" + method.getName());
}
if (paramTypes[1] != int.class) {
throw new RuntimeException("Second argument should be property index: " +
cls.getName() + "#" + method.getName());
}
Class<?> propTypeClass = paramTypes[2];
String[] names = annotation.names();
if (propTypeClass == int.class) {
for (int i = 0; i < names.length; i++) {
props.put(
names[i],
new GroupIntSetter(annotation, method, i, annotation.defaultInt()));
}
} else if (propTypeClass == float.class) {
for (int i = 0; i < names.length; i++) {
props.put(
names[i],
new GroupFloatSetter(annotation, method, i, annotation.defaultFloat()));
}
} else if (propTypeClass == Integer.class) {
for (int i = 0; i < names.length; i++) {
props.put(
names[i],
new GroupBoxedIntSetter(annotation, method, i));
}
} else {
throw new RuntimeException("Unrecognized type: " + paramTypes[2] + " for method: " +
cls.getName() + "#" + method.getName());
}
}
}
}
CLASS_PROPS_CACHE.put(cls, props);
return props;
}
}

View File

@ -69,15 +69,15 @@ public class ReactViewManager extends ViewGroupManager<ReactViewGroup> {
}
@Override
public Map<String, UIProp.Type> getNativeProps() {
Map<String, UIProp.Type> nativeProps = super.getNativeProps();
public Map<String, String> getNativeProps() {
Map<String, String> nativeProps = super.getNativeProps();
Map<String, UIProp.Type> baseProps = BaseViewPropertyApplicator.getCommonProps();
for (Map.Entry<String, UIProp.Type> entry : baseProps.entrySet()) {
nativeProps.put(entry.getKey(), entry.getValue());
nativeProps.put(entry.getKey(), entry.getValue().toString());
}
for (int i = 0; i < SPACING_TYPES.length; i++) {
nativeProps.put(ViewProps.BORDER_WIDTHS[i], UIProp.Type.NUMBER);
nativeProps.put(PROPS_BORDER_COLOR[i], UIProp.Type.STRING);
nativeProps.put(ViewProps.BORDER_WIDTHS[i], UIProp.Type.NUMBER.toString());
nativeProps.put(PROPS_BORDER_COLOR[i], UIProp.Type.STRING.toString());
}
return nativeProps;
}