diff --git a/android/plugins/litho/build.gradle b/android/plugins/litho/build.gradle index 3d3f37dbb..9a6f1e12d 100644 --- a/android/plugins/litho/build.gradle +++ b/android/plugins/litho/build.gradle @@ -21,6 +21,7 @@ android { compileOnly deps.lithoAnnotations implementation project(':android') implementation deps.lithoCore + implementation deps.lithoEditorCore implementation deps.lithoSectionsDebug implementation deps.lithoSectionsCore implementation deps.lithoWidget diff --git a/android/plugins/litho/src/main/java/com/facebook/flipper/plugins/litho/DataUtils.java b/android/plugins/litho/src/main/java/com/facebook/flipper/plugins/litho/DataUtils.java index 23032ccca..292f8a50a 100644 --- a/android/plugins/litho/src/main/java/com/facebook/flipper/plugins/litho/DataUtils.java +++ b/android/plugins/litho/src/main/java/com/facebook/flipper/plugins/litho/DataUtils.java @@ -73,11 +73,7 @@ public class DataUtils { props.put(f.getName(), InspectorValue.immutable(description)); } } else { - if (isTypeMutable(f.getType())) { - props.put(f.getName(), InspectorValue.mutable(f.get(node))); - } else { - props.put(f.getName(), InspectorValue.immutable(f.get(node))); - } + props.put(f.getName(), FlipperEditor.makeFlipperField(node, f)); } break; } @@ -93,7 +89,7 @@ public class DataUtils { } @Nullable - static FlipperObject getStateData(Object node, StateContainer stateContainer) throws Exception { + static FlipperObject getStateData(StateContainer stateContainer) { if (stateContainer == null) { return null; } @@ -106,11 +102,7 @@ public class DataUtils { final State annotation = f.getAnnotation(State.class); if (annotation != null) { - if (DataUtils.isTypeMutable(f.getType())) { - state.put(f.getName(), InspectorValue.mutable(f.get(stateContainer))); - } else { - state.put(f.getName(), InspectorValue.immutable(f.get(stateContainer))); - } + state.put(f.getName(), FlipperEditor.makeFlipperField(stateContainer, f)); hasState = true; } } @@ -118,23 +110,6 @@ public class DataUtils { return hasState ? state.build() : null; } - static boolean isTypeMutable(Class type) { - if (type == int.class || type == Integer.class) { - return true; - } else if (type == long.class || type == Long.class) { - return true; - } else if (type == float.class || type == Float.class) { - return true; - } else if (type == double.class || type == Double.class) { - return true; - } else if (type == boolean.class || type == Boolean.class) { - return true; - } else if (type.isAssignableFrom(String.class)) { - return true; - } - return false; - } - static com.facebook.flipper.plugins.inspector.InspectorValue fromDrawable(Drawable d) { int color = 0; if (d instanceof ColorDrawable) { diff --git a/android/plugins/litho/src/main/java/com/facebook/flipper/plugins/litho/DebugComponentDescriptor.java b/android/plugins/litho/src/main/java/com/facebook/flipper/plugins/litho/DebugComponentDescriptor.java index 4812184ad..f3eee0a58 100644 --- a/android/plugins/litho/src/main/java/com/facebook/flipper/plugins/litho/DebugComponentDescriptor.java +++ b/android/plugins/litho/src/main/java/com/facebook/flipper/plugins/litho/DebugComponentDescriptor.java @@ -58,7 +58,7 @@ public class DebugComponentDescriptor extends NodeDescriptor { for (Pair override : overrides) { if (override.first[0].equals("Props")) { - applyReflectiveOverride(component, override.first[1], override.second); + applyReflectiveOverride(component, override.first, override.second); } } } @@ -72,7 +72,7 @@ public class DebugComponentDescriptor extends NodeDescriptor { for (Pair override : overrides) { if (override.first[0].equals("State")) { - applyReflectiveOverride(stateContainer, override.first[1], override.second); + applyReflectiveOverride(stateContainer, override.first, override.second); } } } @@ -278,8 +278,8 @@ public class DebugComponentDescriptor extends NodeDescriptor { } @Nullable - private static FlipperObject getStateData(DebugComponent node) throws Exception { - return DataUtils.getStateData(node, node.getStateContainer()); + private static FlipperObject getStateData(DebugComponent node) { + return DataUtils.getStateData(node.getStateContainer()); } @Override @@ -502,31 +502,13 @@ public class DebugComponentDescriptor extends NodeDescriptor { return YogaEdge.valueOf(s.toUpperCase()); } - private static void applyReflectiveOverride(Object o, String key, FlipperDynamic dynamic) { + // The path follows the pattern (Props|State)/field/(field|index)* + private static void applyReflectiveOverride( + Object o, final String[] path, final FlipperDynamic dynamic) { try { - final Field field = o.getClass().getDeclaredField(key); - field.setAccessible(true); + final Field field = o.getClass().getDeclaredField(path[1]); + FlipperEditor.updateComponent(path, field, o, dynamic); - final Class type = field.getType(); - - Object value = null; - if (type == int.class || type == Integer.class) { - value = dynamic.asInt(); - } else if (type == long.class || type == Long.class) { - value = dynamic.asLong(); - } else if (type == float.class || type == Float.class) { - value = dynamic.asFloat(); - } else if (type == double.class || type == Double.class) { - value = dynamic.asDouble(); - } else if (type == boolean.class || type == Boolean.class) { - value = dynamic.asBoolean(); - } else if (type.isAssignableFrom(String.class)) { - value = dynamic.asString(); - } - - if (value != null) { - field.set(o, value); - } } catch (Exception ignored) { } } diff --git a/android/plugins/litho/src/main/java/com/facebook/flipper/plugins/litho/DebugSectionDescriptor.java b/android/plugins/litho/src/main/java/com/facebook/flipper/plugins/litho/DebugSectionDescriptor.java index 882c4992d..b7f7bd520 100644 --- a/android/plugins/litho/src/main/java/com/facebook/flipper/plugins/litho/DebugSectionDescriptor.java +++ b/android/plugins/litho/src/main/java/com/facebook/flipper/plugins/litho/DebugSectionDescriptor.java @@ -91,8 +91,8 @@ public class DebugSectionDescriptor extends NodeDescriptor { return DataUtils.getPropData(section); } - private static @Nullable FlipperObject getStateData(DebugSection node) throws Exception { - return DataUtils.getStateData(node, node.getStateContainer()); + private static @Nullable FlipperObject getStateData(DebugSection node) { + return DataUtils.getStateData(node.getStateContainer()); } @Override diff --git a/android/plugins/litho/src/main/java/com/facebook/flipper/plugins/litho/FlipperEditor.java b/android/plugins/litho/src/main/java/com/facebook/flipper/plugins/litho/FlipperEditor.java new file mode 100644 index 000000000..771e5b0fb --- /dev/null +++ b/android/plugins/litho/src/main/java/com/facebook/flipper/plugins/litho/FlipperEditor.java @@ -0,0 +1,125 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + * + * 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.flipper.plugins.litho; + +import com.facebook.flipper.core.FlipperArray; +import com.facebook.flipper.core.FlipperDynamic; +import com.facebook.flipper.core.FlipperObject; +import com.facebook.flipper.core.FlipperValue; +import com.facebook.flipper.plugins.inspector.InspectorValue; +import com.facebook.litho.editor.EditorRegistry; +import com.facebook.litho.editor.model.EditorArray; +import com.facebook.litho.editor.model.EditorBool; +import com.facebook.litho.editor.model.EditorNumber; +import com.facebook.litho.editor.model.EditorShape; +import com.facebook.litho.editor.model.EditorString; +import com.facebook.litho.editor.model.EditorValue; +import java.lang.reflect.Field; +import java.util.HashMap; +import java.util.Map; +import javax.annotation.Nullable; + +/** + * This class is responsible for making Litho Editor compatible with Flipper. + * + *

It provides methods to convert from FlipperDynamic, and to provide the description of a Prop + * or State as a FlipperObject, FlipperArray or FlipperValue. + */ +public class FlipperEditor { + /** + * Uses an editor to create a FlipperObject, FlipperArray or FlipperValue to describe it. If no + * editor is available then it returns the class name. + */ + public static Object makeFlipperField(Object node, Field f) { + Class type = f.getType(); + final EditorValue editorValue = EditorRegistry.read(type, f, node); + if (editorValue != null) { + return intoFlipper(editorValue); + } else { + return InspectorValue.immutable(type.toString()); + } + } + + /** + * Uses an editor to update a field nested in an object path with the value of a FlipperDynamic. + * If no editor is available then it returns null. + * + *

The path as defined by Flipper starts with either "Props" or "State" followed by the call + * chain into the object. Fields retain their name, positions in an array use their index. + */ + public static @Nullable Boolean updateComponent( + String[] path, Field field, Object o, FlipperDynamic dynamic) { + Object raw = dynamic.raw(); + EditorValue edit; + if (raw instanceof String) { + edit = EditorValue.string((String) raw); + } else if (raw instanceof Number) { + edit = EditorValue.number(((Number) raw)); + } else if (raw instanceof Boolean) { + edit = EditorValue.bool((Boolean) raw); + } else { + edit = EditorValue.string(raw.toString()); + } + for (int i = path.length - 1; i > 0; i--) { + HashMap content = new HashMap<>(); + content.put(path[i], edit); + edit = EditorValue.shape(content); + } + return EditorRegistry.write(field.getType(), field, o, edit); + } + + /** Converts into one of FlipperValue, FlipperObject, or FlipperArray */ + public static Object intoFlipper(EditorValue editorValue) { + return editorValue.when( + new EditorValue.EditorVisitor() { + @Override + public Object isShape(EditorShape object) { + FlipperObject.Builder bb = new FlipperObject.Builder(); + + for (Map.Entry entry : object.value.entrySet()) { + bb.put(entry.getKey(), intoFlipper(entry.getValue())); + } + + return bb.build(); + } + + @Override + public Object isArray(EditorArray array) { + FlipperArray.Builder bb = new FlipperArray.Builder(); + + for (EditorValue entry : array.value) { + Object flipper = intoFlipper(entry); + if (flipper instanceof FlipperValue) { + bb.put((FlipperValue) flipper); + } else if (flipper instanceof FlipperObject) { + bb.put((FlipperObject) flipper); + } else if (flipper instanceof FlipperArray) { + bb.put((FlipperArray) flipper); + } + } + + return bb.build(); + } + + @Override + public Object isNumber(EditorNumber number) { + return InspectorValue.mutable(number.value); + } + + @Override + public Object isString(EditorString string) { + return InspectorValue.mutable(string.value); + } + + @Override + public Object isBool(EditorBool bool) { + return InspectorValue.mutable(bool.value); + } + }); + } +} diff --git a/android/src/main/java/com/facebook/flipper/core/FlipperDynamic.java b/android/src/main/java/com/facebook/flipper/core/FlipperDynamic.java index a676c3893..7287a4900 100644 --- a/android/src/main/java/com/facebook/flipper/core/FlipperDynamic.java +++ b/android/src/main/java/com/facebook/flipper/core/FlipperDynamic.java @@ -18,6 +18,10 @@ public class FlipperDynamic { mObject = object; } + public Object raw() { + return mObject; + } + public @Nullable String asString() { if (mObject == null) { return null; diff --git a/build.gradle b/build.gradle index 7459f9778..8260f7e6c 100644 --- a/build.gradle +++ b/build.gradle @@ -72,6 +72,7 @@ ext.deps = [ // Litho lithoAnnotations : "com.facebook.litho:litho-annotations:$LITHO_VERSION", lithoCore : "com.facebook.litho:litho-core:$LITHO_VERSION", + lithoEditorCore : "com.facebook.litho:litho-editor-core:$LITHO_VERSION", lithoSectionsAnnotations: "com.facebook.litho:litho-sections-annotations:$LITHO_VERSION", lithoSectionsDebug : "com.facebook.litho:litho-sections-debug:$LITHO_VERSION", lithoSectionsCore : "com.facebook.litho:litho-sections-core:$LITHO_VERSION", diff --git a/gradle.properties b/gradle.properties index a081296a3..eebb2329b 100644 --- a/gradle.properties +++ b/gradle.properties @@ -18,7 +18,7 @@ POM_DEVELOPER_NAME=facebook POM_ISSUES_URL = 'https://github.com/facebook/flipper/issues/' # Shared version numbers -LITHO_VERSION=0.36.0 +LITHO_VERSION=0.37.1 ANDROIDX_VERSION=1.1.0 KOTLIN_VERSION=1.3.71