From 0562178739ce53b2b6d9e1005fb790366fcae35a Mon Sep 17 00:00:00 2001 From: Luke De Feo Date: Tue, 13 Sep 2022 11:05:42 -0700 Subject: [PATCH] Basic Litho support Summary: Added an initial litho Tree observer and descriptors, its quiet naive and will be improved in a future diff Reviewed By: lblasa Differential Revision: D39466931 fbshipit-source-id: 66a462882af2e585b9719ee2f61595449f99c5e5 --- android/plugins/litho/build.gradle | 1 + .../uidebugger/litho/LithoDescriptors.kt | 59 +++++++++++++++++++ .../plugins/uidebugger/litho/LithoObserver.kt | 53 +++++++++++++++++ .../litho/UIDebuggerLithoSupport.kt | 25 ++++++++ .../flipper/sample/FlipperInitializer.java | 13 +++- .../uidebugger/UIDebuggerFlipperPlugin.kt | 10 +++- .../observers/DecorViewTreeObserver.kt | 11 ++++ .../uidebugger/UIDebuggerFlipperPluginTest.kt | 8 ++- 8 files changed, 175 insertions(+), 5 deletions(-) create mode 100644 android/plugins/litho/src/main/java/com/facebook/flipper/plugins/uidebugger/litho/LithoDescriptors.kt create mode 100644 android/plugins/litho/src/main/java/com/facebook/flipper/plugins/uidebugger/litho/LithoObserver.kt create mode 100644 android/plugins/litho/src/main/java/com/facebook/flipper/plugins/uidebugger/litho/UIDebuggerLithoSupport.kt diff --git a/android/plugins/litho/build.gradle b/android/plugins/litho/build.gradle index e9f636e3c..eabe18840 100644 --- a/android/plugins/litho/build.gradle +++ b/android/plugins/litho/build.gradle @@ -6,6 +6,7 @@ */ apply plugin: 'com.android.library' +apply plugin: 'kotlin-android' android { compileSdkVersion rootProject.compileSdkVersion diff --git a/android/plugins/litho/src/main/java/com/facebook/flipper/plugins/uidebugger/litho/LithoDescriptors.kt b/android/plugins/litho/src/main/java/com/facebook/flipper/plugins/uidebugger/litho/LithoDescriptors.kt new file mode 100644 index 000000000..22fb2b027 --- /dev/null +++ b/android/plugins/litho/src/main/java/com/facebook/flipper/plugins/uidebugger/litho/LithoDescriptors.kt @@ -0,0 +1,59 @@ +/* + * 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.litho + +import com.facebook.flipper.plugins.uidebugger.common.InspectableObject +import com.facebook.flipper.plugins.uidebugger.descriptors.Descriptor +import com.facebook.litho.DebugComponent +import com.facebook.litho.LithoView + +object LithoViewDescriptor : Descriptor() { + override fun getId(node: LithoView): String = System.identityHashCode(node).toString() + + override fun getName(node: LithoView): String = "LithoView" + + override fun getChildren(node: LithoView, children: MutableList) { + val debugComponent = DebugComponent.getRootInstance(node) + if (debugComponent != null) { + children.add(debugComponent) + } + } + + override fun getActiveChild(node: LithoView): Any? = null + + override fun getData(node: LithoView, builder: MutableMap) {} +} + +object DebugComponentDescriptor : Descriptor() { + override fun getId(node: DebugComponent): String = System.identityHashCode(node).toString() + + override fun getName(node: DebugComponent): String { + return node.component.simpleName + } + + // TODO the mutable list thing doesnt make sense for non chained descriptors, should just return + override fun getChildren(node: DebugComponent, children: MutableList) { + val mountedView = node.mountedView + val mountedDrawable = node.mountedDrawable + + if (mountedView != null) { + children.add(mountedView) + } else if (mountedDrawable != null) { + children.add(mountedDrawable) + } else { + for (child in node.childComponents) { + children.add(child) + } + } + } + + override fun getActiveChild(node: DebugComponent): Any? = null + + // todo same here + override fun getData(node: DebugComponent, builder: MutableMap) {} +} diff --git a/android/plugins/litho/src/main/java/com/facebook/flipper/plugins/uidebugger/litho/LithoObserver.kt b/android/plugins/litho/src/main/java/com/facebook/flipper/plugins/uidebugger/litho/LithoObserver.kt new file mode 100644 index 000000000..e1c0ec837 --- /dev/null +++ b/android/plugins/litho/src/main/java/com/facebook/flipper/plugins/uidebugger/litho/LithoObserver.kt @@ -0,0 +1,53 @@ +package com.facebook.flipper.plugins.uidebugger.litho + +import com.facebook.flipper.plugins.uidebugger.SubtreeUpdate +import com.facebook.flipper.plugins.uidebugger.TreeObserver +import com.facebook.flipper.plugins.uidebugger.core.Context +import com.facebook.flipper.plugins.uidebugger.identityHashCode +import com.facebook.flipper.plugins.uidebugger.observers.TreeObserverBuilder +import com.facebook.litho.LithoView + +class LithoViewTreeObserver(val context: Context) : TreeObserver() { + + var nodeRef: LithoView? = null + override fun subscribe(node: Any) { + node as LithoView + + nodeRef = node + + val listener: (view: LithoView) -> Unit = { + val start = System.currentTimeMillis() + + val (nodes, skipped) = context.layoutTraversal.traverse(it) + + for (observerRoot in skipped) { + if (!children.containsKey(observerRoot.identityHashCode())) { + val observer = context.observerFactory.createObserver(observerRoot, context)!! + observer.subscribe(observerRoot) + children[observerRoot.identityHashCode()] = observer + } + } + + context.treeObserverManager.emit( + SubtreeUpdate("Litho", nodes, start, System.currentTimeMillis())) + } + node.setOnDirtyMountListener(listener) + + listener(node) + } + + override fun unsubscribe() { + nodeRef?.setOnDirtyMountListener(null) + nodeRef = null + } +} + +object LithoViewTreeObserverBuilder : TreeObserverBuilder { + override fun canBuildFor(node: Any): Boolean { + return node is LithoView + } + + override fun build(context: Context): TreeObserver { + return LithoViewTreeObserver(context) + } +} diff --git a/android/plugins/litho/src/main/java/com/facebook/flipper/plugins/uidebugger/litho/UIDebuggerLithoSupport.kt b/android/plugins/litho/src/main/java/com/facebook/flipper/plugins/uidebugger/litho/UIDebuggerLithoSupport.kt new file mode 100644 index 000000000..2be140361 --- /dev/null +++ b/android/plugins/litho/src/main/java/com/facebook/flipper/plugins/uidebugger/litho/UIDebuggerLithoSupport.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.litho + +import com.facebook.flipper.plugins.uidebugger.descriptors.DescriptorRegister +import com.facebook.flipper.plugins.uidebugger.observers.TreeObserverFactory +import com.facebook.litho.DebugComponent +import com.facebook.litho.LithoView + +object UIDebuggerLithoSupport { + + fun addDescriptors(register: DescriptorRegister) { + register.register(LithoView::class.java, LithoViewDescriptor) + register.register(DebugComponent::class.java, DebugComponentDescriptor) + } + + fun addObserver(observerFactory: TreeObserverFactory) { + observerFactory.register(LithoViewTreeObserverBuilder) + } +} diff --git a/android/sample/src/debug/java/com/facebook/flipper/sample/FlipperInitializer.java b/android/sample/src/debug/java/com/facebook/flipper/sample/FlipperInitializer.java index b45cbce57..2bb361175 100644 --- a/android/sample/src/debug/java/com/facebook/flipper/sample/FlipperInitializer.java +++ b/android/sample/src/debug/java/com/facebook/flipper/sample/FlipperInitializer.java @@ -22,6 +22,9 @@ import com.facebook.flipper.plugins.network.NetworkFlipperPlugin; import com.facebook.flipper.plugins.sharedpreferences.SharedPreferencesFlipperPlugin; import com.facebook.flipper.plugins.sharedpreferences.SharedPreferencesFlipperPlugin.SharedPreferencesDescriptor; import com.facebook.flipper.plugins.uidebugger.UIDebuggerFlipperPlugin; +import com.facebook.flipper.plugins.uidebugger.descriptors.DescriptorRegister; +import com.facebook.flipper.plugins.uidebugger.litho.UIDebuggerLithoSupport; +import com.facebook.flipper.plugins.uidebugger.observers.TreeObserverFactory; import com.facebook.litho.config.ComponentsConfiguration; import com.facebook.litho.editor.flipper.LithoFlipperDescriptors; import java.util.Arrays; @@ -56,7 +59,15 @@ public final class FlipperInitializer { client.addPlugin(CrashReporterPlugin.getInstance()); client.addPlugin(new DatabasesFlipperPlugin(context)); client.addPlugin(NavigationFlipperPlugin.getInstance()); - client.addPlugin(new UIDebuggerFlipperPlugin((Application) context)); + + DescriptorRegister descriptorRegister = DescriptorRegister.Companion.withDefaults(); + TreeObserverFactory treeObserverFactory = TreeObserverFactory.Companion.withDefaults(); + UIDebuggerLithoSupport.INSTANCE.addDescriptors(descriptorRegister); + UIDebuggerLithoSupport.INSTANCE.addObserver(treeObserverFactory); + + client.addPlugin( + new UIDebuggerFlipperPlugin( + (Application) context, descriptorRegister, treeObserverFactory)); client.start(); final OkHttpClient okHttpClient = 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 7334bb458..b77d017a5 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 @@ -21,14 +21,18 @@ import kotlinx.serialization.json.Json const val LogTag = "FlipperUIDebugger" -class UIDebuggerFlipperPlugin(val application: Application) : FlipperPlugin { +class UIDebuggerFlipperPlugin( + val application: Application, + descriptorRegister: DescriptorRegister?, + observerFactory: TreeObserverFactory? +) : FlipperPlugin { private val context: Context = Context( ApplicationRef(application), ConnectionRef(null), - DescriptorRegister.withDefaults(), - TreeObserverFactory.withDefaults()) + descriptorRegister = descriptorRegister ?: DescriptorRegister.withDefaults(), + observerFactory = observerFactory ?: TreeObserverFactory.withDefaults()) private val nativeScanScheduler = Scheduler(NativeScanScheduler(context)) diff --git a/android/src/main/java/com/facebook/flipper/plugins/uidebugger/observers/DecorViewTreeObserver.kt b/android/src/main/java/com/facebook/flipper/plugins/uidebugger/observers/DecorViewTreeObserver.kt index cae214627..a216dcb11 100644 --- a/android/src/main/java/com/facebook/flipper/plugins/uidebugger/observers/DecorViewTreeObserver.kt +++ b/android/src/main/java/com/facebook/flipper/plugins/uidebugger/observers/DecorViewTreeObserver.kt @@ -14,6 +14,7 @@ import com.facebook.flipper.plugins.uidebugger.LogTag import com.facebook.flipper.plugins.uidebugger.SubtreeUpdate import com.facebook.flipper.plugins.uidebugger.TreeObserver import com.facebook.flipper.plugins.uidebugger.core.Context +import com.facebook.flipper.plugins.uidebugger.identityHashCode typealias DecorView = View @@ -40,6 +41,16 @@ class DecorViewObserver(val context: Context) : TreeObserver() { val start = System.currentTimeMillis() if (start - lastSend > throttleTimeMs) { val (nodes, skipped) = context.layoutTraversal.traverse(node) + + for (observerRoot in skipped) { + + if (!children.containsKey(observerRoot.identityHashCode())) { + val observer = context.observerFactory.createObserver(observerRoot, context)!! + observer.subscribe(observerRoot) + children[observerRoot.identityHashCode()] = observer + } + } + val traversalComplete = System.currentTimeMillis() context.treeObserverManager.emit( SubtreeUpdate("DecorView", nodes, start, traversalComplete)) diff --git a/android/src/test/java/com/facebook/flipper/plugins/uidebugger/UIDebuggerFlipperPluginTest.kt b/android/src/test/java/com/facebook/flipper/plugins/uidebugger/UIDebuggerFlipperPluginTest.kt index d691ef1b5..a8bda3c79 100644 --- a/android/src/test/java/com/facebook/flipper/plugins/uidebugger/UIDebuggerFlipperPluginTest.kt +++ b/android/src/test/java/com/facebook/flipper/plugins/uidebugger/UIDebuggerFlipperPluginTest.kt @@ -8,6 +8,8 @@ package com.facebook.flipper.plugins.uidebugger import com.facebook.flipper.plugins.uidebugger.core.ApplicationRef +import com.facebook.flipper.plugins.uidebugger.descriptors.DescriptorRegister +import com.facebook.flipper.plugins.uidebugger.observers.TreeObserverFactory import org.junit.Assert import org.junit.Before import org.junit.Test @@ -30,7 +32,11 @@ class UIDebuggerFlipperPluginTest { @Throws(Exception::class) @Test fun emptyTest() { - var plugin = UIDebuggerFlipperPlugin(app) + var plugin = + UIDebuggerFlipperPlugin( + app, + DescriptorRegister.Companion.withDefaults(), + TreeObserverFactory.Companion.withDefaults()) Assert.assertNotNull(plugin) } }