forked from facebook/litho
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add ability to integrate a Events listener into a ComponentTree
Summary: As part of the process of replacing `ComponentsLogger` with an API that relies on the new Events API, I'm adding the ability to listen to events associated with a ComponentTree. These events can be ComponentTree/RenderCore related and they will use the `renderStateId/componentTreeId` as the reference. ## Client Side The client will only define a `ComponentTreeDebugEventListener` where it specifies the action to perform on an `DebugEvent` and the set of events it is interested in. ## Internals Whenever a listener is present, we will wrap it inside a `DebugEventSubscriber` which will only propagate the events related to that ComponentTree and for which the listener is interested in. This subscriber will be automatically subscriber and will unsubscribe during `release`. Reviewed By: adityasharat Differential Revision: D46080849 fbshipit-source-id: 2c2ade11b89feede22e037615aca915a3d3a1288
- Loading branch information
1 parent
e204991
commit d71cdbe
Showing
6 changed files
with
273 additions
and
24 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
83 changes: 83 additions & 0 deletions
83
litho-core/src/main/java/com/facebook/litho/LithoEventListener.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,83 @@ | ||
/* | ||
* Copyright (c) Meta Platforms, Inc. and affiliates. | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
|
||
package com.facebook.litho | ||
|
||
import com.facebook.rendercore.debug.DebugEvent | ||
import com.facebook.rendercore.debug.DebugEventSubscriber | ||
|
||
/** | ||
* This is used as a listener for events that happen on the scope of a given [ComponentTree]. These | ||
* events can be related with both the `render` UI pipeline, or with the view-side events created by | ||
* rendercore in the [LithoView] that is associated to the [ComponentTree] to which this listener | ||
* observes. | ||
* | ||
* In order to specify which events you are interested in, you should override the | ||
* [ComponentTreeDebugEventListener.events] and list any of the events present in [LithoDebugEvent] | ||
* or [DebugEvent]. | ||
* | ||
* A client can attach one of these listeners during the creation of a [ComponentTree]: | ||
* ``` | ||
* val listener = object: ComponentTreeDebugEventListener { | ||
* override fun onEvent(debugEvent: DebugEvent) { | ||
* /* do your logging / business logic */ | ||
* } | ||
* | ||
* override val events = setOf(LayoutCommitted, MountItemMount) | ||
* } | ||
* | ||
* val componentTree = ComponentTree.create(context, MyComponent()) | ||
* .withComponentTreeDebugEventListener(listener) | ||
* .create() | ||
* | ||
* val lithoView = LithoView.create(context, componentTree) | ||
* ``` | ||
*/ | ||
interface ComponentTreeDebugEventListener { | ||
|
||
fun onEvent(debugEvent: DebugEvent) | ||
|
||
val events: Set<String> | ||
} | ||
|
||
/** | ||
* This abstraction acts as a wrapper which guarantees that it will act only in events related to | ||
* the [componentTreeId]. | ||
* | ||
* This is meant to be used only by the internals of the [ComponentTree], by wrapping any | ||
* [ComponentTreeDebugEventListener] passed in by the clients. This is a mechanism so that we can | ||
* guarantee that the client's listener will only observe events related to the [ComponentTree] to | ||
* which it is associated. | ||
* | ||
* *Note:* ideally this code would belong inside the [ComponentTree], but to keep it in Kotlin we | ||
* are leaving it outside it until finish the Kotlin migration. | ||
*/ | ||
internal class ComponentTreeDebugEventsSubscriber( | ||
componentTreeId: Int, | ||
eventsToObserve: Set<String>, | ||
private val onComponentTreeEvent: (DebugEvent) -> Unit, | ||
) : DebugEventSubscriber(*eventsToObserve.toTypedArray()) { | ||
|
||
private val componentTreeIdStr = componentTreeId.toString() | ||
|
||
override fun onEvent(event: DebugEvent) { | ||
if (event.renderStateId != componentTreeIdStr) { | ||
return | ||
} | ||
|
||
onComponentTreeEvent(event) | ||
} | ||
} |
130 changes: 130 additions & 0 deletions
130
litho-it/src/test/java/com/facebook/litho/ComponentTreeDebugEventListenerTest.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,130 @@ | ||
/* | ||
* Copyright (c) Meta Platforms, Inc. and affiliates. | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
|
||
package com.facebook.litho | ||
|
||
import android.content.Context | ||
import androidx.test.core.app.ApplicationProvider.getApplicationContext | ||
import com.facebook.litho.debug.LithoDebugEvent.LayoutCommitted | ||
import com.facebook.litho.kotlin.widget.Text | ||
import com.facebook.litho.testing.LithoViewRule | ||
import com.facebook.litho.testing.testrunner.LithoTestRunner | ||
import com.facebook.rendercore.debug.DebugEvent | ||
import com.facebook.rendercore.debug.DebugEventBus | ||
import com.facebook.rendercore.debug.DebugEventDispatcher | ||
import org.assertj.core.api.Assertions.assertThat | ||
import org.junit.After | ||
import org.junit.Before | ||
import org.junit.Rule | ||
import org.junit.Test | ||
import org.junit.runner.RunWith | ||
|
||
@RunWith(LithoTestRunner::class) | ||
class ComponentTreeDebugEventListenerTest { | ||
|
||
@get:Rule val rule = LithoViewRule() | ||
|
||
@Before | ||
fun setup() { | ||
DebugEventBus.enabled = true | ||
} | ||
|
||
@After | ||
fun tearDown() { | ||
DebugEventBus.enabled = false | ||
} | ||
|
||
@Test | ||
fun `should process events if associated with a ComponentTree`() { | ||
val listener = TestListener() | ||
|
||
val componentContext = ComponentContext(getApplicationContext() as Context) | ||
val componentTree = | ||
ComponentTree.create(componentContext, EmptyComponent()) | ||
.withComponentTreeDebugEventListener(listener) | ||
.build() | ||
|
||
rule.render(componentTree = componentTree) { MyComponent() } | ||
rule.idle() | ||
|
||
assertThat(listener.observedEvents).hasSize(1) | ||
assertThat(listener.observedEvents.first().type).isEqualTo(LayoutCommitted) | ||
} | ||
|
||
@Test | ||
fun `should not process events if not associated with a ComponentTree`() { | ||
val listener = TestListener() | ||
|
||
val componentContext = ComponentContext(getApplicationContext() as Context) | ||
val componentTree = | ||
ComponentTree.create(componentContext, EmptyComponent()) | ||
.withComponentTreeDebugEventListener(null) | ||
.build() | ||
|
||
rule.render(componentTree = componentTree) { MyComponent() } | ||
rule.idle() | ||
|
||
assertThat(listener.observedEvents).hasSize(0) | ||
} | ||
|
||
@Test | ||
fun `should not process any component tree event once released`() { | ||
val listener = TestListener() | ||
|
||
val componentContext = ComponentContext(getApplicationContext() as Context) | ||
val componentTree = | ||
ComponentTree.create(componentContext, EmptyComponent()) | ||
.withComponentTreeDebugEventListener(listener) | ||
.build() | ||
|
||
rule.render(componentTree = componentTree) { MyComponent() } | ||
rule.idle() | ||
|
||
assertThat(listener.observedEvents).hasSize(1) | ||
assertThat(listener.observedEvents.last().type).isEqualTo(LayoutCommitted) | ||
|
||
/* if we dispatch the event we are looking for we will process it */ | ||
DebugEventDispatcher.dispatch( | ||
type = LayoutCommitted, renderStateId = { componentTree.mId.toString() }) | ||
assertThat(listener.observedEvents).hasSize(2) | ||
assertThat(listener.observedEvents.last().type).isEqualTo(LayoutCommitted) | ||
|
||
/* if we dispatch the event we are looking for after the Component Tree is released, it will not | ||
be processed */ | ||
componentTree.release() | ||
DebugEventDispatcher.dispatch( | ||
type = LayoutCommitted, renderStateId = { componentTree.mId.toString() }) | ||
assertThat(listener.observedEvents).hasSize(2) | ||
} | ||
|
||
private class TestListener : ComponentTreeDebugEventListener { | ||
|
||
val observedEvents: List<DebugEvent> | ||
get() = _observedEvents | ||
|
||
private val _observedEvents = mutableListOf<DebugEvent>() | ||
|
||
override fun onEvent(debugEvent: DebugEvent) { | ||
_observedEvents.add(debugEvent) | ||
} | ||
|
||
override val events: Set<String> = setOf(LayoutCommitted) | ||
} | ||
|
||
private class MyComponent : KComponent() { | ||
override fun ComponentScope.render(): Component = Text("Hello") | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters