diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/ReactShadowNode.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/ReactShadowNode.java index c4d5aa7b7a18d1..e98a1f39e78e76 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/ReactShadowNode.java +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/ReactShadowNode.java @@ -12,9 +12,12 @@ import javax.annotation.Nullable; import java.util.ArrayList; +import java.util.Map; import com.facebook.csslayout.CSSNode; import com.facebook.infer.annotation.Assertions; +import com.facebook.react.bridge.ReadableMap; +import com.facebook.react.bridge.ReadableMapKeySeyIterator; /** * Base node class for representing virtual tree of React nodes. Shadow nodes are used primarily @@ -23,7 +26,7 @@ * {@link CSSNode} by adding additional capabilities. * * Instances of this class receive property updates from JS via @{link UIManagerModule}. Subclasses - * may use {@link #updateProperties} to persist some of the updated fields in the node instance that + * may use {@link #updateShadowNode} to persist some of the updated fields in the node instance that * corresponds to a particular view type. * * Subclasses of {@link ReactShadowNode} should be created only from {@link ViewManager} that @@ -159,7 +162,34 @@ public ReactShadowNode removeChildAt(int i) { public void onBeforeLayout() { } - public void updateProperties(CatalystStylesDiffMap styles) { + public final void updateProperties(CatalystStylesDiffMap props) { + Map propSetters = + ViewManagersPropertyCache.getNativePropSettersForShadowNodeClass(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.updateShadowNodeProp(this, props); + } else { + missingSetters = true; + } + } + if (missingSetters) { + updateShadowNode(props); + } + onAfterUpdateTransaction(); + } + + public void onAfterUpdateTransaction() { + // no-op + } + + @Deprecated + public void updateShadowNode(CatalystStylesDiffMap styles) { BaseCSSPropertyApplicator.applyCSSProperties(this, styles); } @@ -235,7 +265,7 @@ protected void setThemedContext(ThemedReactContext themedContext) { mThemedContext = themedContext; } - /* package */ void setShouldNotifyOnLayout(boolean shouldNotifyOnLayout) { + public void setShouldNotifyOnLayout(boolean shouldNotifyOnLayout) { mShouldNotifyOnLayout = shouldNotifyOnLayout; } diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/ViewManager.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/ViewManager.java index 012771882c2e89..726c5f76548abc 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/ViewManager.java +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/ViewManager.java @@ -231,7 +231,7 @@ public Map getNativeProps() { // refactoring is finished Class cls = getClass(); Map nativeProps = - ViewManagersPropertyCache.getNativePropsForView(cls); + ViewManagersPropertyCache.getNativePropsForView(cls, getShadowNodeClass()); while (cls.getSuperclass() != null) { Map props = getNativePropsForClass(cls); for (Map.Entry entry : props.entrySet()) { diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/ViewManagersPropertyCache.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/ViewManagersPropertyCache.java index bb076b38dd1c39..c34430cc6480f6 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/ViewManagersPropertyCache.java +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/ViewManagersPropertyCache.java @@ -36,6 +36,8 @@ // thread sequentially private static final Object[] VIEW_MGR_ARGS = new Object[2]; private static final Object[] VIEW_MGR_GROUP_ARGS = new Object[3]; + private static final Object[] SHADOW_ARGS = new Object[1]; + private static final Object[] SHADOW_GROUP_ARGS = new Object[2]; private PropSetter(ReactProp prop, String defaultType, Method setter) { mPropName = prop.name(); @@ -83,6 +85,25 @@ public void updateViewProp( } } + public void updateShadowNodeProp( + ReactShadowNode nodeToUpdate, + CatalystStylesDiffMap props) { + try { + if (mIndex == null) { + SHADOW_ARGS[0] = extractProperty(props); + mSetter.invoke(nodeToUpdate, SHADOW_ARGS); + } else { + SHADOW_GROUP_ARGS[0] = mIndex; + SHADOW_GROUP_ARGS[1] = extractProperty(props); + mSetter.invoke(nodeToUpdate, SHADOW_GROUP_ARGS); + } + } catch (Throwable t) { + FLog.e(ViewManager.class, "Error while updating prop " + mPropName, t); + throw new JSApplicationIllegalArgumentException("Error while updating property '" + + mPropName + "' in shadow node of type: " + nodeToUpdate.getViewClass(), t); + } + } + protected abstract @Nullable Object extractProperty(CatalystStylesDiffMap props); } @@ -227,7 +248,8 @@ public BoxedIntPropSetter(ReactPropGroup prop, Method setter, int index) { } /*package*/ static Map getNativePropsForView( - Class viewManagerTopClass) { + Class viewManagerTopClass, + Class shadowNodeTopClass) { Map nativeProps = new HashMap<>(); Map viewManagerProps = @@ -236,12 +258,18 @@ public BoxedIntPropSetter(ReactPropGroup prop, Method setter, int index) { nativeProps.put(setter.getPropName(), setter.getPropType()); } + Map shadowNodeProps = + getNativePropSettersForShadowNodeClass(shadowNodeTopClass); + for (PropSetter setter : shadowNodeProps.values()) { + nativeProps.put(setter.getPropName(), setter.getPropType()); + } + return nativeProps; } /** * Returns map from property name to setter instances for all the property setters annotated with - * {@link ReactProp} in the given {@link ViewManager} plus all the setter declared by it's + * {@link ReactProp} in the given {@link ViewManager} class plus all the setter declared by its * parent classes. */ /*package*/ static Map getNativePropSettersForViewManagerClass( @@ -263,6 +291,30 @@ public BoxedIntPropSetter(ReactPropGroup prop, Method setter, int index) { return props; } + /** + * Returns map from property name to setter instances for all the property setters annotated with + * {@link ReactProp} (or {@link ReactPropGroup} in the given {@link ReactShadowNode} subclass plus + * all the setters declared by its parent classes up to {@link ReactShadowNode} which is treated + * as a base class. + */ + /*package*/ static Map getNativePropSettersForShadowNodeClass( + Class cls) { + if (cls == ReactShadowNode.class) { + return EMPTY_PROPS_MAP; + } + Map props = CLASS_PROPS_CACHE.get(cls); + if (props != null) { + return props; + } + // This is to include all the setters from parent classes up to ReactShadowNode class + props = new HashMap<>( + getNativePropSettersForShadowNodeClass( + (Class) cls.getSuperclass())); + extractPropSettersFromShadowNodeClassDefinition(cls, props); + CLASS_PROPS_CACHE.put(cls, props); + return props; + } + private static PropSetter createPropSetter( ReactProp annotation, Method method, @@ -360,4 +412,34 @@ private static void extractPropSettersFromViewManagerClassDefinition( } } } + + private static void extractPropSettersFromShadowNodeClassDefinition( + Class cls, + Map props) { + for (Method method : cls.getDeclaredMethods()) { + ReactProp annotation = method.getAnnotation(ReactProp.class); + if (annotation != null) { + Class[] paramTypes = method.getParameterTypes(); + if (paramTypes.length != 1) { + throw new RuntimeException("Wrong number of args for prop setter: " + + cls.getName() + "#" + method.getName()); + } + props.put(annotation.name(), createPropSetter(annotation, method, paramTypes[0])); + } + + ReactPropGroup groupAnnotation = method.getAnnotation(ReactPropGroup.class); + if (groupAnnotation != null) { + Class [] paramTypes = method.getParameterTypes(); + if (paramTypes.length != 2) { + throw new RuntimeException("Wrong number of args for group prop setter: " + + cls.getName() + "#" + method.getName()); + } + if (paramTypes[0] != int.class) { + throw new RuntimeException("Second argument should be property index: " + + cls.getName() + "#" + method.getName()); + } + createPropSetters(groupAnnotation, method, paramTypes[1], props); + } + } + } } diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/progressbar/ProgressBarShadowNode.java b/ReactAndroid/src/main/java/com/facebook/react/views/progressbar/ProgressBarShadowNode.java index b6d9f315ff2199..e9df4cc3c6ac7d 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/progressbar/ProgressBarShadowNode.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/progressbar/ProgressBarShadowNode.java @@ -69,8 +69,8 @@ public void measure(CSSNode node, float width, MeasureOutput measureOutput) { } @Override - public void updateProperties(CatalystStylesDiffMap styles) { - super.updateProperties(styles); + public void updateShadowNode(CatalystStylesDiffMap styles) { + super.updateShadowNode(styles); if (styles.hasKey(ReactProgressBarViewManager.PROP_STYLE)) { String style = styles.getString(ReactProgressBarViewManager.PROP_STYLE); diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextShadowNode.java b/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextShadowNode.java index a841e2c9acb9fd..8841ced48609b6 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextShadowNode.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextShadowNode.java @@ -288,8 +288,8 @@ protected void markUpdated() { } @Override - public void updateProperties(CatalystStylesDiffMap styles) { - super.updateProperties(styles); + public void updateShadowNode(CatalystStylesDiffMap styles) { + super.updateShadowNode(styles); if (styles.hasKey(PROP_TEXT)) { mText = styles.getString(PROP_TEXT); diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextInputShadowNode.java b/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextInputShadowNode.java index 8d60d3abe62075..14e77cdc255457 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextInputShadowNode.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextInputShadowNode.java @@ -97,8 +97,8 @@ public void onBeforeLayout() { } @Override - public void updateProperties(CatalystStylesDiffMap styles) { - super.updateProperties(styles); + public void updateShadowNode(CatalystStylesDiffMap styles) { + super.updateShadowNode(styles); if (styles.hasKey(ViewProps.FONT_SIZE)) { float fontSize = styles.getFloat(ViewProps.FONT_SIZE, ViewDefaults.FONT_SIZE_SP); mFontSize = (int) Math.ceil(PixelUtil.toPixelFromSP(fontSize));