From f123e65e8fb161f91ac8ccd8c5011ad4c63b1645 Mon Sep 17 00:00:00 2001 From: Luke De Feo Date: Wed, 7 Sep 2022 04:37:17 -0700 Subject: [PATCH] Added strongly typed tree for sidebar inspector Summary: Introduced a JSON like tree structure for the sidebar insepector. Descriptors can provide their data as well as if its mutable or not. Enum mapping was simplified Reviewed By: lblasa Differential Revision: D38947238 fbshipit-source-id: cd8a6a8a752c5f626582ab8ac5efae6e9ff6a2ad --- .../descriptors/utils/EnumMapping.java | 10 - .../flipper/plugins/uidebugger/Events.kt | 25 ++ .../uidebugger/UIDebuggerFlipperPlugin.kt | 23 +- .../plugins/uidebugger/common/EnumMapping.kt | 80 ++---- .../plugins/uidebugger/common/Exceptions.kt | 10 + .../plugins/uidebugger/common/Inspectable.kt | 99 +++++++ .../uidebugger/common/InspectableValue.kt | 54 ---- .../flipper/plugins/uidebugger/common/Node.kt | 7 +- .../uidebugger/core/LayoutTraversal.kt | 6 +- .../descriptors/AbstractChainedDescriptor.kt | 24 +- .../descriptors/ActivityDescriptor.kt | 6 +- .../descriptors/ApplicationDescriptor.kt | 6 +- .../descriptors/ButtonDescriptor.kt | 7 +- .../uidebugger/descriptors/NodeDescriptor.kt | 8 +- .../descriptors/ObjectDescriptor.kt | 4 +- .../descriptors/TextViewDescriptor.kt | 7 +- .../uidebugger/descriptors/ViewDescriptor.kt | 268 ++++++++++-------- .../descriptors/ViewGroupDescriptor.kt | 42 +-- .../descriptors/WindowDescriptor.kt | 7 +- .../plugins/uidebugger/EnumMappingTest.kt | 61 ++-- 20 files changed, 418 insertions(+), 336 deletions(-) create mode 100644 android/src/main/java/com/facebook/flipper/plugins/uidebugger/Events.kt create mode 100644 android/src/main/java/com/facebook/flipper/plugins/uidebugger/common/Exceptions.kt create mode 100644 android/src/main/java/com/facebook/flipper/plugins/uidebugger/common/Inspectable.kt delete mode 100644 android/src/main/java/com/facebook/flipper/plugins/uidebugger/common/InspectableValue.kt diff --git a/android/src/main/java/com/facebook/flipper/plugins/inspector/descriptors/utils/EnumMapping.java b/android/src/main/java/com/facebook/flipper/plugins/inspector/descriptors/utils/EnumMapping.java index d7edcbd05..a987d63ab 100644 --- a/android/src/main/java/com/facebook/flipper/plugins/inspector/descriptors/utils/EnumMapping.java +++ b/android/src/main/java/com/facebook/flipper/plugins/inspector/descriptors/utils/EnumMapping.java @@ -52,16 +52,6 @@ public class EnumMapping { return mMapping.get(mDefaultKey); } - public InspectorValue toPicker() { - return toPicker(true); - } - - public InspectorValue toPicker(final boolean mutable) { - return mutable - ? InspectorValue.mutable(Picker, new InspectorValue.Picker(mMapping.keySet(), mDefaultKey)) - : InspectorValue.immutable(Enum, mDefaultKey); - } - public InspectorValue toPicker(final T currentValue) { return toPicker(currentValue, true); } diff --git a/android/src/main/java/com/facebook/flipper/plugins/uidebugger/Events.kt b/android/src/main/java/com/facebook/flipper/plugins/uidebugger/Events.kt new file mode 100644 index 000000000..306935cec --- /dev/null +++ b/android/src/main/java/com/facebook/flipper/plugins/uidebugger/Events.kt @@ -0,0 +1,25 @@ +/* + * Copyright (c) Meta Platforms, Inc. and 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.uidebugger + +import com.facebook.flipper.plugins.uidebugger.common.Node + +@kotlinx.serialization.Serializable +data class InitEvent(val rootId: String) { + companion object { + val name = "init" + } +} + +// TODO flatten the tree into normalised list +@kotlinx.serialization.Serializable +data class NativeScanEvent(val root: Node) { + companion object { + val name = "nativeScan" + } +} diff --git a/android/src/main/java/com/facebook/flipper/plugins/uidebugger/UIDebuggerFlipperPlugin.kt b/android/src/main/java/com/facebook/flipper/plugins/uidebugger/UIDebuggerFlipperPlugin.kt index 7eefc8ca1..bec805f8f 100644 --- a/android/src/main/java/com/facebook/flipper/plugins/uidebugger/UIDebuggerFlipperPlugin.kt +++ b/android/src/main/java/com/facebook/flipper/plugins/uidebugger/UIDebuggerFlipperPlugin.kt @@ -9,11 +9,14 @@ package com.facebook.flipper.plugins.uidebugger import android.app.Application import com.facebook.flipper.core.FlipperConnection -import com.facebook.flipper.core.FlipperObject import com.facebook.flipper.core.FlipperPlugin +import com.facebook.flipper.plugins.uidebugger.common.Node import com.facebook.flipper.plugins.uidebugger.core.ApplicationInspector import com.facebook.flipper.plugins.uidebugger.core.ApplicationRef import com.facebook.flipper.plugins.uidebugger.core.Context +import kotlinx.serialization.json.Json + +val LogTag = "FlipperUIDebugger" class UIDebuggerFlipperPlugin(val application: Application) : FlipperPlugin { @@ -28,14 +31,18 @@ class UIDebuggerFlipperPlugin(val application: Application) : FlipperPlugin { override fun onConnect(connection: FlipperConnection) { this.connection = connection // temp solution, get from descriptor - connection.send( - "init", - FlipperObject.Builder() - .put("rootId", System.identityHashCode(application).toString()) - .build()) - val inspector = ApplicationInspector(context) - val root = inspector.inspect() + val root: Node = inspector.inspect()!! + val initEvent = InitEvent(System.identityHashCode(application).toString()) + + connection.send( + InitEvent.name, + Json.encodeToString( + InitEvent.serializer(), InitEvent(System.identityHashCode(application).toString()))) + + connection.send( + NativeScanEvent.name, + Json.encodeToString(NativeScanEvent.serializer(), NativeScanEvent(root))) } @Throws(Exception::class) diff --git a/android/src/main/java/com/facebook/flipper/plugins/uidebugger/common/EnumMapping.kt b/android/src/main/java/com/facebook/flipper/plugins/uidebugger/common/EnumMapping.kt index e26a996ed..dbea0b0c9 100644 --- a/android/src/main/java/com/facebook/flipper/plugins/uidebugger/common/EnumMapping.kt +++ b/android/src/main/java/com/facebook/flipper/plugins/uidebugger/common/EnumMapping.kt @@ -7,60 +7,36 @@ package com.facebook.flipper.plugins.uidebugger.common -import androidx.collection.ArrayMap -import androidx.collection.SimpleArrayMap +import android.util.Log +import com.facebook.flipper.plugins.uidebugger.LogTag -open class EnumMapping(private val defaultKey: String) { - private val map = ArrayMap() +// Maintains 2 way mapping between some enum value and a readable string representation +open class EnumMapping(val mapping: Map) { - fun put(key: String, value: T) { - map.put(key, value) - } - - fun get(value: T): InspectableValue { - return get(value, true) - } - - fun get(value: T, mutable: Boolean = true): InspectableValue { - val key = findKeyForValue(map, defaultKey, value) - return if (mutable) InspectableValue.mutable(InspectableValue.Type.Enum, key) - else InspectableValue.immutable(InspectableValue.Type.Enum, key) - } - - fun get(s: String): T? { - return if (map.containsKey(s)) { - map[s] - } else map[defaultKey] - } - - fun toPicker(mutable: Boolean = true): InspectableValue<*> { - return if (mutable) - InspectableValue.mutable( - InspectableValue.Type.Picker, InspectableValue.Picker(map.keys, defaultKey)) - else InspectableValue.immutable(InspectableValue.Type.Enum, defaultKey) - } - - fun toPicker(currentValue: T, mutable: Boolean = true): InspectableValue<*> { - val value = findKeyForValue(map, defaultKey, currentValue) - return if (mutable) - InspectableValue.mutable( - InspectableValue.Type.Picker, InspectableValue.Picker(map.keys, value)) - else InspectableValue.immutable(InspectableValue.Type.Enum, value) - } - - companion object { - fun findKeyForValue( - mapping: SimpleArrayMap, - defaultKey: String, - currentValue: T - ): String { - val count = mapping.size() - 1 - for (i in 0..count) { - if (mapping.valueAt(i) == currentValue) { - return mapping.keyAt(i) - } - } - return defaultKey + fun getStringRepresentation(enumValue: T): String { + val entry = mapping.entries.find { (_, value) -> value == enumValue } + if (entry != null) { + return entry.key + } else { + Log.w( + LogTag, + "Could not convert enum value ${enumValue.toString()} to string, known values ${mapping.entries}") + return NoMapping } } + + fun getEnumValue(key: String): T { + val value = + mapping[key] + ?: throw UIDebuggerException( + "Could not convert string ${key} to enum value, possible values ${mapping.entries} ") + return value + } + + fun toInspectable(value: T, mutable: Boolean): InspectableValue.Enum { + return InspectableValue.Enum(EnumData(mapping.keys, getStringRepresentation(value)), mutable) + } + companion object { + val NoMapping = "__UNKNOWN_ENUM_VALUE__" + } } diff --git a/android/src/main/java/com/facebook/flipper/plugins/uidebugger/common/Exceptions.kt b/android/src/main/java/com/facebook/flipper/plugins/uidebugger/common/Exceptions.kt new file mode 100644 index 000000000..f5efa9f16 --- /dev/null +++ b/android/src/main/java/com/facebook/flipper/plugins/uidebugger/common/Exceptions.kt @@ -0,0 +1,10 @@ +/* + * Copyright (c) Meta Platforms, Inc. and 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.uidebugger.common + +class UIDebuggerException(message: String) : Exception(message) diff --git a/android/src/main/java/com/facebook/flipper/plugins/uidebugger/common/Inspectable.kt b/android/src/main/java/com/facebook/flipper/plugins/uidebugger/common/Inspectable.kt new file mode 100644 index 000000000..4e9e06909 --- /dev/null +++ b/android/src/main/java/com/facebook/flipper/plugins/uidebugger/common/Inspectable.kt @@ -0,0 +1,99 @@ +/* + * Copyright (c) Meta Platforms, Inc. and 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.uidebugger.common + +import kotlinx.serialization.KSerializer +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable +import kotlinx.serialization.descriptors.PrimitiveKind +import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor +import kotlinx.serialization.descriptors.SerialDescriptor +import kotlinx.serialization.encoding.Decoder +import kotlinx.serialization.encoding.Encoder + +@kotlinx.serialization.Serializable +sealed class Inspectable { + abstract val mutable: kotlin.Boolean +} + +// mutable here means you can add/remove items, for native android this should probably be false +@SerialName("array") +@Serializable +data class InspectableArray(val items: List, override val mutable: Boolean = false) : + Inspectable() + +// mutable here means you can add / remove keys, for native android this should probably be false +@SerialName("object") +@Serializable +data class InspectableObject( + val fields: Map, + override val mutable: Boolean = false +) : Inspectable() + +@kotlinx.serialization.Serializable +sealed class InspectableValue : Inspectable() { + + @kotlinx.serialization.Serializable + @SerialName("text") + class Text(val value: String, override val mutable: kotlin.Boolean) : InspectableValue() + + @kotlinx.serialization.Serializable + @SerialName("boolean") + class Boolean(val value: kotlin.Boolean, override val mutable: kotlin.Boolean) : + InspectableValue() + + @SerialName("number") + @kotlinx.serialization.Serializable + data class Number( + @Serializable(with = NumberSerializer::class) val value: kotlin.Number, + override val mutable: kotlin.Boolean + ) : InspectableValue() + + @SerialName("color") + @kotlinx.serialization.Serializable + data class Color(val value: Int, override val mutable: kotlin.Boolean) : InspectableValue() + + @SerialName("enum") + @kotlinx.serialization.Serializable + data class Enum(val value: EnumData, override val mutable: kotlin.Boolean) : InspectableValue() + + companion object { + /** + * Will attempt to convert Any ref to a suitable primitive inspectable value. Only use if you + * are dealing with an Any / object type. Prefer the specific contructors + */ + fun fromAny(any: Any, mutable: kotlin.Boolean): Inspectable? { + return when (any) { + is kotlin.Number -> InspectableValue.Number(any, mutable) + is kotlin.Boolean -> InspectableValue.Boolean(any, mutable) + is kotlin.String -> InspectableValue.Text(any, mutable) + else -> null + } + } + } +} + +@kotlinx.serialization.Serializable data class EnumData(val values: Set, val value: String) + +object NumberSerializer : KSerializer { + override val descriptor: SerialDescriptor = + PrimitiveSerialDescriptor("com.meta.NumberSerializer", PrimitiveKind.DOUBLE) + + override fun serialize(encoder: Encoder, value: Number) { + when (value) { + is Double -> encoder.encodeDouble(value.toDouble()) + is Float -> encoder.encodeFloat(value.toFloat()) + is Long -> encoder.encodeLong(value.toLong()) + is Int -> encoder.encodeInt(value.toInt()) + } + } + + override fun deserialize(decoder: Decoder): Number { + return decoder.decodeFloat() + } +} diff --git a/android/src/main/java/com/facebook/flipper/plugins/uidebugger/common/InspectableValue.kt b/android/src/main/java/com/facebook/flipper/plugins/uidebugger/common/InspectableValue.kt deleted file mode 100644 index ccd16432a..000000000 --- a/android/src/main/java/com/facebook/flipper/plugins/uidebugger/common/InspectableValue.kt +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright (c) Meta Platforms, Inc. and 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.uidebugger.common - -class InspectableValue -private constructor(val type: Type, val value: T, val mutable: Boolean) { - /** - * Describe the type of data this value contains. This will influence how values are parsed and - * displayed by the Flipper desktop app. For example colors will be parse as integers and - * displayed using hex values and be editable using a color picker. - * - * Do not extends this list of types without adding support for the type in the desktop Inspector. - */ - class Type internal constructor(private val name: String) { - override fun toString(): String { - return name - } - - companion object { - val Auto: Type = Type("auto") - val Text = Type("text") - val Number = Type("number") - val Boolean = Type("boolean") - val Enum = Type("enum") - val Color = Type("color") - val Picker = Type("picker") - } - } - - class Picker(val values: Set, val selected: String) {} - - companion object { - fun mutable(type: Type, value: T): InspectableValue { - return InspectableValue(type, value, true) - } - - fun immutable(type: Type, value: T): InspectableValue { - return InspectableValue(type, value, false) - } - - fun mutable(value: Any): InspectableValue<*> { - return InspectableValue(Type.Auto, value, true) - } - - fun immutable(value: Any): InspectableValue<*> { - return InspectableValue(Type.Auto, value, false) - } - } -} diff --git a/android/src/main/java/com/facebook/flipper/plugins/uidebugger/common/Node.kt b/android/src/main/java/com/facebook/flipper/plugins/uidebugger/common/Node.kt index 52a979acc..77a71a300 100644 --- a/android/src/main/java/com/facebook/flipper/plugins/uidebugger/common/Node.kt +++ b/android/src/main/java/com/facebook/flipper/plugins/uidebugger/common/Node.kt @@ -7,11 +7,10 @@ package com.facebook.flipper.plugins.uidebugger.common -import java.lang.ref.WeakReference - -class Node(val ref: WeakReference) { +@kotlinx.serialization.Serializable +class Node() { var id: String? = null var name: String? = null - var attributes: Map? = null + var attributes: Map = mapOf() var children: List? = null } diff --git a/android/src/main/java/com/facebook/flipper/plugins/uidebugger/core/LayoutTraversal.kt b/android/src/main/java/com/facebook/flipper/plugins/uidebugger/core/LayoutTraversal.kt index 890657488..5eddc7c34 100644 --- a/android/src/main/java/com/facebook/flipper/plugins/uidebugger/core/LayoutTraversal.kt +++ b/android/src/main/java/com/facebook/flipper/plugins/uidebugger/core/LayoutTraversal.kt @@ -7,10 +7,10 @@ package com.facebook.flipper.plugins.uidebugger.core +import com.facebook.flipper.plugins.uidebugger.common.InspectableObject import com.facebook.flipper.plugins.uidebugger.common.Node import com.facebook.flipper.plugins.uidebugger.descriptors.Descriptor import com.facebook.flipper.plugins.uidebugger.descriptors.DescriptorRegister -import java.lang.ref.WeakReference class LayoutTraversal(private val descriptorRegister: DescriptorRegister) { class IntermediateNode(val node: Node) { @@ -20,7 +20,7 @@ class LayoutTraversal(private val descriptorRegister: DescriptorRegister) { internal inline fun Descriptor<*>.asAny(): Descriptor = this as Descriptor private fun describe(obj: Any): IntermediateNode { - var intermediate = IntermediateNode(Node(WeakReference(obj))) + var intermediate = IntermediateNode(Node()) val descriptor = descriptorRegister.descriptorForClass(obj::class.java) descriptor?.let { descriptor -> @@ -29,7 +29,7 @@ class LayoutTraversal(private val descriptorRegister: DescriptorRegister) { intermediate.node.id = anyDescriptor.getId(obj) intermediate.node.name = anyDescriptor.getName(obj) - val attributes = mutableMapOf() + val attributes = mutableMapOf() anyDescriptor.getData(obj, attributes) intermediate.node.attributes = attributes diff --git a/android/src/main/java/com/facebook/flipper/plugins/uidebugger/descriptors/AbstractChainedDescriptor.kt b/android/src/main/java/com/facebook/flipper/plugins/uidebugger/descriptors/AbstractChainedDescriptor.kt index 6b161359c..2d57d301b 100644 --- a/android/src/main/java/com/facebook/flipper/plugins/uidebugger/descriptors/AbstractChainedDescriptor.kt +++ b/android/src/main/java/com/facebook/flipper/plugins/uidebugger/descriptors/AbstractChainedDescriptor.kt @@ -7,13 +7,15 @@ package com.facebook.flipper.plugins.uidebugger.descriptors +import com.facebook.flipper.plugins.uidebugger.common.InspectableObject + /** * This class derives from Descriptor and provides a canonical implementation of ChainedDescriptor}. */ abstract class AbstractChainedDescriptor : Descriptor(), ChainedDescriptor { private var mSuper: Descriptor? = null - override fun setSuper(superDescriptor: Descriptor) { + final override fun setSuper(superDescriptor: Descriptor) { if (superDescriptor !== mSuper) { check(mSuper == null) mSuper = superDescriptor @@ -25,7 +27,7 @@ abstract class AbstractChainedDescriptor : Descriptor(), ChainedDescriptor } /** Initialize a descriptor. */ - override fun init() { + final override fun init() { mSuper?.init() onInit() } @@ -36,7 +38,7 @@ abstract class AbstractChainedDescriptor : Descriptor(), ChainedDescriptor * A globally unique ID used to identify a node in a hierarchy. If your node does not have a * globally unique ID it is fine to rely on [System.identityHashCode]. */ - override fun getId(node: T): String { + final override fun getId(node: T): String { return onGetId(node) } @@ -46,28 +48,28 @@ abstract class AbstractChainedDescriptor : Descriptor(), ChainedDescriptor * The name used to identify this node in the inspector. Does not need to be unique. A good * default is to use the class name of the node. */ - override fun getName(node: T): String { + final override fun getName(node: T): String { return onGetName(node) } abstract fun onGetName(node: T): String /** The children this node exposes in the inspector. */ - override fun getChildren(node: T, children: MutableList) { + final override fun getChildren(node: T, children: MutableList) { mSuper?.getChildren(node, children) onGetChildren(node, children) } open fun onGetChildren(node: T, children: MutableList) {} - /** - * Get the data to show for this node in the sidebar of the inspector. The object will be showen - * in order and with a header matching the given name. - */ - override fun getData(node: T, builder: MutableMap) { + final override fun getData(node: T, builder: MutableMap) { mSuper?.getData(node, builder) onGetData(node, builder) } - open fun onGetData(node: T, builder: MutableMap) {} + /** + * Get the data to show for this node in the sidebar of the inspector. Each key will be a have its + * own section + */ + open fun onGetData(node: T, attributeSections: MutableMap) {} } diff --git a/android/src/main/java/com/facebook/flipper/plugins/uidebugger/descriptors/ActivityDescriptor.kt b/android/src/main/java/com/facebook/flipper/plugins/uidebugger/descriptors/ActivityDescriptor.kt index e3accf52c..dcb8089a0 100644 --- a/android/src/main/java/com/facebook/flipper/plugins/uidebugger/descriptors/ActivityDescriptor.kt +++ b/android/src/main/java/com/facebook/flipper/plugins/uidebugger/descriptors/ActivityDescriptor.kt @@ -8,6 +8,7 @@ package com.facebook.flipper.plugins.uidebugger.descriptors import android.app.Activity +import com.facebook.flipper.plugins.uidebugger.common.InspectableObject import com.facebook.flipper.plugins.uidebugger.stetho.FragmentCompat class ActivityDescriptor : AbstractChainedDescriptor() { @@ -35,7 +36,10 @@ class ActivityDescriptor : AbstractChainedDescriptor() { } } - override fun onGetData(activity: Activity, builder: MutableMap) {} + override fun onGetData( + activity: Activity, + attributeSections: MutableMap + ) {} private fun getFragments(compat: FragmentCompat<*, *, *, *>?, activity: Activity): List { if (compat == null) { diff --git a/android/src/main/java/com/facebook/flipper/plugins/uidebugger/descriptors/ApplicationDescriptor.kt b/android/src/main/java/com/facebook/flipper/plugins/uidebugger/descriptors/ApplicationDescriptor.kt index b21d806b1..bb3002df2 100644 --- a/android/src/main/java/com/facebook/flipper/plugins/uidebugger/descriptors/ApplicationDescriptor.kt +++ b/android/src/main/java/com/facebook/flipper/plugins/uidebugger/descriptors/ApplicationDescriptor.kt @@ -8,6 +8,7 @@ package com.facebook.flipper.plugins.uidebugger.descriptors import android.app.Activity +import com.facebook.flipper.plugins.uidebugger.common.InspectableObject import com.facebook.flipper.plugins.uidebugger.core.ApplicationRef import com.facebook.flipper.plugins.uidebugger.core.RootViewResolver @@ -48,5 +49,8 @@ class ApplicationDescriptor : AbstractChainedDescriptor() { } } - override fun onGetData(applicationRef: ApplicationRef, builder: MutableMap) {} + override fun onGetData( + applicationRef: ApplicationRef, + attributeSections: MutableMap + ) {} } diff --git a/android/src/main/java/com/facebook/flipper/plugins/uidebugger/descriptors/ButtonDescriptor.kt b/android/src/main/java/com/facebook/flipper/plugins/uidebugger/descriptors/ButtonDescriptor.kt index f87b4e515..e7c3af119 100644 --- a/android/src/main/java/com/facebook/flipper/plugins/uidebugger/descriptors/ButtonDescriptor.kt +++ b/android/src/main/java/com/facebook/flipper/plugins/uidebugger/descriptors/ButtonDescriptor.kt @@ -8,9 +8,9 @@ package com.facebook.flipper.plugins.uidebugger.descriptors import android.widget.Button +import com.facebook.flipper.plugins.uidebugger.common.InspectableObject class ButtonDescriptor : AbstractChainedDescriptor