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 index e1c0ec837..1ee202859 100644 --- 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 @@ -1,42 +1,37 @@ +/* + * 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.SubtreeUpdate +import android.util.Log +import com.facebook.flipper.plugins.uidebugger.LogTag 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() { + override val type = "Litho" + var nodeRef: LithoView? = null + override fun subscribe(node: Any) { - node as LithoView - nodeRef = node + nodeRef = node as LithoView - 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())) - } + val listener: (view: LithoView) -> Unit = { traverseAndSend(context, node) } node.setOnDirtyMountListener(listener) listener(node) } override fun unsubscribe() { + Log.i(LogTag, "Unsubscribing from litho view") nodeRef?.setOnDirtyMountListener(null) nodeRef = null } diff --git a/android/src/main/java/com/facebook/flipper/plugins/uidebugger/observers/ApplicationTreeObserver.kt b/android/src/main/java/com/facebook/flipper/plugins/uidebugger/observers/ApplicationTreeObserver.kt index 4374a2156..a0c312adb 100644 --- a/android/src/main/java/com/facebook/flipper/plugins/uidebugger/observers/ApplicationTreeObserver.kt +++ b/android/src/main/java/com/facebook/flipper/plugins/uidebugger/observers/ApplicationTreeObserver.kt @@ -24,6 +24,8 @@ import com.facebook.flipper.plugins.uidebugger.identityHashCode */ class ApplicationTreeObserver(val context: Context) : TreeObserver() { + override val type = "Application" + override fun subscribe(node: Any) { Log.i(LogTag, "subscribing to application / activity changes") 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 a216dcb11..2e398961c 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 @@ -11,10 +11,8 @@ import android.util.Log import android.view.View import android.view.ViewTreeObserver 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 @@ -23,10 +21,12 @@ class DecorViewObserver(val context: Context) : TreeObserver() { val throttleTimeMs = 500 - // maybe should be weak reference in ctor? + // should this be a weak reference? private var nodeRef: View? = null private var listener: ViewTreeObserver.OnPreDrawListener? = null + override val type = "DecorView" + override fun subscribe(node: Any) { node as View @@ -38,22 +38,9 @@ class DecorViewObserver(val context: Context) : TreeObserver() { object : ViewTreeObserver.OnPreDrawListener { var lastSend = 0L override fun onPreDraw(): Boolean { - val start = System.currentTimeMillis() - if (start - lastSend > throttleTimeMs) { - val (nodes, skipped) = context.layoutTraversal.traverse(node) + if (System.currentTimeMillis() - lastSend > throttleTimeMs) { + traverseAndSend(context, 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)) lastSend = System.currentTimeMillis() } return true @@ -64,11 +51,9 @@ class DecorViewObserver(val context: Context) : TreeObserver() { } override fun unsubscribe() { - Log.i(LogTag, "Try Unsubscribing to decor view changes") + Log.i(LogTag, "Unsubscribing from decor view changes") listener.let { - Log.i(LogTag, "Actually Unsubscribing to decor view changes") - nodeRef?.viewTreeObserver?.removeOnPreDrawListener(it) listener = null nodeRef = null diff --git a/android/src/main/java/com/facebook/flipper/plugins/uidebugger/observers/TreeObserver.kt b/android/src/main/java/com/facebook/flipper/plugins/uidebugger/observers/TreeObserver.kt index c7931bbe5..d7960a908 100644 --- a/android/src/main/java/com/facebook/flipper/plugins/uidebugger/observers/TreeObserver.kt +++ b/android/src/main/java/com/facebook/flipper/plugins/uidebugger/observers/TreeObserver.kt @@ -103,7 +103,7 @@ It is responsible for: 1. listening to the relevant framework events 2. Traversing the hierarchy of the managed nodes 3. Diffing to previous state (optional) - 4. Pushing out updates for its entire set of nodes + 4. Pushing out updates for its entire set of managed nodes If while traversing it encounters a node type which has its own TreeObserver, it does not traverse that, instead it sets up a Tree observer responsible for that subtree @@ -114,12 +114,51 @@ abstract class TreeObserver { protected val children: MutableMap> = mutableMapOf() - // todo try to pass T again? + abstract val type: String + abstract fun subscribe(node: Any) abstract fun unsubscribe() + /** + * Optional helper method that traverses the layout hierarchy while managing any encountered child + * observers correctly + */ + fun traverseAndSend(context: Context, root: Any) { + val start = System.currentTimeMillis() + val (visitedNodes, observerRootsNodes) = context.layoutTraversal.traverse(root) + + // Add any new Observers + for (observerRoot in observerRootsNodes) { + + if (!children.containsKey(observerRoot.identityHashCode())) { + val childObserver = context.observerFactory.createObserver(observerRoot, context)!! + Log.d( + LogTag, + "For Observer ${this.type} discovered new child of type ${childObserver.type} Node ID ${observerRoot.identityHashCode()}") + childObserver.subscribe(observerRoot) + children[observerRoot.identityHashCode()] = childObserver + } + } + + // remove any old observers + val observerRootIds = observerRootsNodes.map { it.identityHashCode() } + for (childKey in children.keys) { + if (!observerRootIds.contains(childKey)) { + + Log.d( + LogTag, + "For Observer ${this.type} cleaning up child of type ${children[childKey]!!.type} Node ID ${childKey}") + + children[childKey]!!.cleanUpRecursive() + } + } + context.treeObserverManager.emit( + SubtreeUpdate(type, visitedNodes, start, System.currentTimeMillis())) + } + fun cleanUpRecursive() { + Log.i(LogTag, "Cleaning up observer ${this}") children.values.forEach { it.cleanUpRecursive() } unsubscribe() children.clear() @@ -192,7 +231,6 @@ class PartialLayoutTraversal( val attributes = mutableMapOf() descriptor.getData(node, attributes) - // NOTE active child null here visited.add( Node( descriptor.getId(node),