Use mount extension for litho integration
Summary: Initial implementation of Litho extensions using mount extension. After mount is called on the main thread and we traverse the hierachy. In future we can use mount extensions to construct a sparse tree rather than sending everything every time. Scroll is handled with a native UI scroll listener for each litho view. This may break if the litho view is not a direct child of the scroll view. Reviewed By: mihaelao Differential Revision: D40021840 fbshipit-source-id: b09086a7a16660225885620609009dddf5b90d3b
This commit is contained in:
committed by
Facebook GitHub Bot
parent
7c3e28272b
commit
1aacc51d12
@@ -20,6 +20,7 @@ android {
|
||||
dependencies {
|
||||
compileOnly deps.lithoAnnotations
|
||||
implementation project(':android')
|
||||
implementation deps.kotlinCoroutinesAndroid
|
||||
implementation deps.lithoCore
|
||||
api deps.lithoEditorCore
|
||||
api(deps.lithoEditorFlipper) {
|
||||
|
||||
@@ -8,38 +8,92 @@
|
||||
package com.facebook.flipper.plugins.uidebugger.litho
|
||||
|
||||
import android.util.Log
|
||||
import android.view.ViewTreeObserver
|
||||
import com.facebook.flipper.plugins.uidebugger.LogTag
|
||||
import com.facebook.flipper.plugins.uidebugger.core.Context
|
||||
import com.facebook.flipper.plugins.uidebugger.descriptors.nodeId
|
||||
import com.facebook.flipper.plugins.uidebugger.observers.TreeObserver
|
||||
import com.facebook.flipper.plugins.uidebugger.observers.TreeObserverBuilder
|
||||
import com.facebook.flipper.plugins.uidebugger.scheduler.throttleLatest
|
||||
import com.facebook.litho.LithoView
|
||||
import com.facebook.rendercore.extensions.ExtensionState
|
||||
import com.facebook.rendercore.extensions.MountExtension
|
||||
import kotlinx.coroutines.*
|
||||
|
||||
/**
|
||||
* There are 2 ways a litho view can update:
|
||||
* 1. a view was added / updated / removed through a mount ,we use the mount extension to capture
|
||||
* these
|
||||
* 2. The user scrolled. This does not cause a mount to the litho view but it may cause new
|
||||
* components to mount as they come on screen On the native side we capture scrolls as it causes the
|
||||
* draw listener to first but but the layout traversal would stop once it sees the lithoview.
|
||||
*
|
||||
* Therefore we need a way to capture the changes in the position of views in a litho view hierarchy
|
||||
* as they are scrolled. A property that seems to hold for litho is if there is a scrolling view in
|
||||
* the heierachy, its direct children are lithoview.
|
||||
*
|
||||
* Given that we are observing a litho view in this class for mount extension we can also attach a
|
||||
* on scroll changed listener to it to be notified by android when it is scrolled. We just need to
|
||||
* then update the bounds for this view as nothing else has changed. If this scroll does lead to a
|
||||
* mount this will be picked up by the mount extension
|
||||
*/
|
||||
class LithoViewTreeObserver(val context: Context) : TreeObserver<LithoView>() {
|
||||
|
||||
override val type = "Litho"
|
||||
private val throttleTimeMs = 500L
|
||||
|
||||
private var nodeRef: LithoView? = null
|
||||
private val waitScope = CoroutineScope(Dispatchers.IO)
|
||||
private val mainScope = CoroutineScope(Dispatchers.Main)
|
||||
var nodeRef: LithoView? = null
|
||||
var onScrollChangedListener: ViewTreeObserver.OnScrollChangedListener? = null
|
||||
|
||||
override fun subscribe(node: Any) {
|
||||
|
||||
Log.i(LogTag, "Subscribing to litho view ${node.nodeId()}")
|
||||
Log.d(LogTag, "Subscribing to litho view ${node.nodeId()}")
|
||||
|
||||
nodeRef = node as LithoView
|
||||
|
||||
val listener: (view: LithoView) -> Unit = { processUpdate(context, node) }
|
||||
node.setOnDirtyMountListener(listener)
|
||||
val lithoDebuggerExtension = LithoDebuggerExtension(this)
|
||||
node.registerUIDebugger(lithoDebuggerExtension)
|
||||
|
||||
listener(node)
|
||||
val throttledUpdate =
|
||||
throttleLatest<Any>(throttleTimeMs, waitScope, mainScope) { node ->
|
||||
// todo only send bounds for the view rather than the entire hierachy
|
||||
processUpdate(context, node)
|
||||
}
|
||||
|
||||
onScrollChangedListener = ViewTreeObserver.OnScrollChangedListener({ throttledUpdate(node) })
|
||||
node.viewTreeObserver.addOnScrollChangedListener(onScrollChangedListener)
|
||||
|
||||
// we have already missed the first mount so we trigger it manually on subscribe
|
||||
processUpdate(context, node)
|
||||
}
|
||||
|
||||
override fun unsubscribe() {
|
||||
Log.i(LogTag, "Unsubscribing from litho view")
|
||||
nodeRef?.setOnDirtyMountListener(null)
|
||||
Log.d(LogTag, "Unsubscribing from litho view ${nodeRef?.nodeId()}")
|
||||
nodeRef?.viewTreeObserver?.removeOnScrollChangedListener(onScrollChangedListener)
|
||||
nodeRef?.unregisterUIDebugger()
|
||||
nodeRef = null
|
||||
}
|
||||
}
|
||||
|
||||
class LithoDebuggerExtension(val observer: LithoViewTreeObserver) : MountExtension<Void?, Void?>() {
|
||||
|
||||
override fun createState(): Void? {
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* The call guaranteed to be called after new layout mounted completely on the main thread.
|
||||
* mounting includes adding updating or removing views from the heriachy
|
||||
*/
|
||||
override fun afterMount(state: ExtensionState<Void?>) {
|
||||
Log.i(LogTag, "After mount called for litho view ${observer.nodeRef?.nodeId()}")
|
||||
// todo sparse update
|
||||
observer.processUpdate(observer.context, state.rootHost as Any)
|
||||
}
|
||||
}
|
||||
|
||||
object LithoViewTreeObserverBuilder : TreeObserverBuilder<LithoView> {
|
||||
override fun canBuildFor(node: Any): Boolean {
|
||||
return node is LithoView
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
#
|
||||
# This source code is licensed under the MIT license found in the
|
||||
# LICENSE file in the root directory of this source tree.
|
||||
|
||||
# POM publishing constants
|
||||
VERSION_NAME=0.171.2-SNAPSHOT
|
||||
GROUP=com.facebook.flipper
|
||||
@@ -17,18 +16,15 @@ POM_LICENCE_DIST=repo
|
||||
POM_DEVELOPER_ID=facebook
|
||||
POM_DEVELOPER_NAME=facebook
|
||||
POM_ISSUES_URL=https://github.com/facebook/flipper/issues/
|
||||
|
||||
# Shared version numbers
|
||||
LITHO_VERSION=0.41.1
|
||||
LITHO_VERSION=0.43.0
|
||||
ANDROIDX_VERSION=1.3.0
|
||||
KOTLIN_VERSION=1.6.20
|
||||
|
||||
# Gradle internals
|
||||
org.gradle.internal.repository.max.retries=10
|
||||
org.gradle.internal.repository.initial.backoff=1250
|
||||
org.gradle.jvmargs=-Xmx2g -Xms512m -XX:MaxPermSize=1024m -XX:+CMSClassUnloadingEnabled
|
||||
systemProp.org.gradle.internal.http.connectionTimeout=120000
|
||||
systemProp.org.gradle.internal.http.socketTimeout=120000
|
||||
|
||||
android.useAndroidX=true
|
||||
android.enableJetifier=true
|
||||
|
||||
Reference in New Issue
Block a user