diff --git a/litho-core/src/main/java/com/facebook/litho/ComponentTree.java b/litho-core/src/main/java/com/facebook/litho/ComponentTree.java index 391b33dc827..f801923c866 100644 --- a/litho-core/src/main/java/com/facebook/litho/ComponentTree.java +++ b/litho-core/src/main/java/com/facebook/litho/ComponentTree.java @@ -370,7 +370,9 @@ protected ComponentTree(Builder builder) { config, LithoTree.Companion.create(this, stateUpdater), "root", - getLithoVisibilityEventsController(), + ComponentsConfiguration.defaultInstance.enableVisibilityFixForNestedLithoView + ? builder.lithoVisibilityEventsController + : getLithoVisibilityEventsController(), null, builder.parentTreePropContainer); @@ -409,8 +411,14 @@ protected ComponentTree(Builder builder) { } } - if (builder.mLifecycleProvider != null) { - subscribeToLifecycleProvider(builder.mLifecycleProvider); + if (ComponentsConfiguration.defaultInstance.enableVisibilityFixForNestedLithoView) { + if (mContext.getLithoVisibilityEventsController() != null) { + subscribeToLifecycleProvider(mContext.getLithoVisibilityEventsController()); + } + } else { + if (builder.lithoVisibilityEventsController != null) { + subscribeToLifecycleProvider(builder.lithoVisibilityEventsController); + } } ComponentTreeDebugEventListener debugEventListener = config.componentsConfig.debugEventListener; @@ -2955,7 +2963,7 @@ public static class Builder { private @Nullable TreeState treeState; private int overrideComponentTreeId = INVALID_ID; private @Nullable MeasureListener mMeasureListener; - private @Nullable LithoVisibilityEventsController mLifecycleProvider; + private @Nullable LithoVisibilityEventsController lithoVisibilityEventsController; private @Nullable RenderUnitIdGenerator mRenderUnitIdGenerator; private @Nullable VisibilityBoundsTransformer visibilityBoundsTransformer; @@ -3002,7 +3010,7 @@ public Builder withRoot(Component root) { public Builder withLithoVisibilityEventsController( @Nullable LithoVisibilityEventsController lifecycleProvider) { - mLifecycleProvider = lifecycleProvider; + lithoVisibilityEventsController = lifecycleProvider; return this; } diff --git a/litho-core/src/main/java/com/facebook/litho/SimpleNestedTreeVisibilityEventsController.kt b/litho-core/src/main/java/com/facebook/litho/SimpleNestedTreeVisibilityEventsController.kt index 5345fd1283b..bb139b7cd2e 100644 --- a/litho-core/src/main/java/com/facebook/litho/SimpleNestedTreeVisibilityEventsController.kt +++ b/litho-core/src/main/java/com/facebook/litho/SimpleNestedTreeVisibilityEventsController.kt @@ -17,6 +17,7 @@ package com.facebook.litho import com.facebook.litho.LithoVisibilityEventsController.LithoVisibilityState +import com.facebook.litho.config.ComponentsConfiguration /** * LithoVisibilityEventsController implementation that can be used to subscribe a nested @@ -53,7 +54,11 @@ class SimpleNestedTreeVisibilityEventsController( LithoVisibilityState.HINT_VISIBLE -> moveToVisibilityState(LithoVisibilityState.HINT_VISIBLE) LithoVisibilityState.HINT_INVISIBLE -> moveToVisibilityState(LithoVisibilityState.HINT_INVISIBLE) - LithoVisibilityState.DESTROYED -> {} + LithoVisibilityState.DESTROYED -> { + if (ComponentsConfiguration.defaultInstance.enableVisibilityFixForNestedLithoView) { + moveToVisibilityState(LithoVisibilityState.DESTROYED) + } + } } } } diff --git a/litho-core/src/main/java/com/facebook/litho/config/ComponentsConfiguration.kt b/litho-core/src/main/java/com/facebook/litho/config/ComponentsConfiguration.kt index c2fc2dcc95d..7f2ad463b7c 100644 --- a/litho-core/src/main/java/com/facebook/litho/config/ComponentsConfiguration.kt +++ b/litho-core/src/main/java/com/facebook/litho/config/ComponentsConfiguration.kt @@ -140,6 +140,7 @@ internal constructor( @JvmField val useFineGrainedViewAttributesExtension: Boolean = false, @JvmField val enableFacadeStateUpdater: Boolean = false, @JvmField val skipSecondIsInWorkingRangeCheck: Boolean = false, + @JvmField val enableVisibilityFixForNestedLithoView: Boolean = false, ) { val shouldAddRootHostViewOrDisableBgFgOutputs: Boolean = @@ -310,6 +311,8 @@ internal constructor( baseConfig.useFineGrainedViewAttributesExtension private var enableFacadeStateUpdater = baseConfig.enableFacadeStateUpdater private var skipSecondIsInWorkingRangeCheck = baseConfig.skipSecondIsInWorkingRangeCheck + private var enableVisibilityFixForNestedLithoView = + baseConfig.enableVisibilityFixForNestedLithoView fun shouldNotifyVisibleBoundsChangeWhenNestedLithoViewBecomesInvisible( enabled: Boolean @@ -408,6 +411,10 @@ internal constructor( skipSecondIsInWorkingRangeCheck = enabled } + fun enableVisibilityFixForNestedLithoView(enabled: Boolean): Builder = also { + enableVisibilityFixForNestedLithoView = enabled + } + fun build(): ComponentsConfiguration { return baseConfig.copy( specsApiStateUpdateDuplicateDetectionEnabled = @@ -442,7 +449,8 @@ internal constructor( skipHostAlphaReset = skipHostAlphaReset, useFineGrainedViewAttributesExtension = useFineGrainedViewAttributesExtension, enableFacadeStateUpdater = enableFacadeStateUpdater, - skipSecondIsInWorkingRangeCheck = skipSecondIsInWorkingRangeCheck) + skipSecondIsInWorkingRangeCheck = skipSecondIsInWorkingRangeCheck, + enableVisibilityFixForNestedLithoView = enableVisibilityFixForNestedLithoView) } } } diff --git a/litho-it/src/test/java/com/facebook/litho/BUCK b/litho-it/src/test/java/com/facebook/litho/BUCK index 8883aa1146e..8f7f0a8ec29 100644 --- a/litho-it/src/test/java/com/facebook/litho/BUCK +++ b/litho-it/src/test/java/com/facebook/litho/BUCK @@ -62,6 +62,7 @@ litho_robolectric4_test( target = "8", target_sdk_levels = ["33"], deps = [ + "//fbandroid/java/com/facebook/testing/robolectric:robolectric", "//fbandroid/third-party/java/guava:guava", "//third-party/kotlin/mockito-kotlin2:mockito-kotlin2", LITHO_ANDROIDSUPPORT_RECYCLERVIEW_TARGET, diff --git a/litho-it/src/test/java/com/facebook/litho/LithoVisibilityEventsControllerForNestedViewTest.kt b/litho-it/src/test/java/com/facebook/litho/LithoVisibilityEventsControllerForNestedViewTest.kt new file mode 100644 index 00000000000..0e0fba3e929 --- /dev/null +++ b/litho-it/src/test/java/com/facebook/litho/LithoVisibilityEventsControllerForNestedViewTest.kt @@ -0,0 +1,104 @@ +/* + * 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.litho.config.ComponentsConfiguration +import com.facebook.litho.core.height +import com.facebook.litho.core.width +import com.facebook.litho.kotlin.widget.Text +import com.facebook.litho.testing.LithoViewRule +import com.facebook.litho.visibility.onInvisible +import com.facebook.litho.visibility.onVisible +import com.facebook.litho.widget.collection.LazyList +import com.facebook.rendercore.sp +import com.facebook.testing.robolectric.WithTestDefaultsRunner +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 +import org.robolectric.annotation.LooperMode + +@LooperMode(LooperMode.Mode.LEGACY) +@RunWith(WithTestDefaultsRunner::class) +class LithoVisibilityEventsControllerForNestedViewTest { + @Rule + @JvmField + val lithoViewRule: LithoViewRule = + LithoViewRule(lithoVisibilityEventsController = { lithoVisibilityEventsControllerDelegate }) + private val lithoVisibilityEventsControllerDelegate: LithoVisibilityEventsController = + LithoVisibilityEventsControllerDelegate() + private val invisibleTags: MutableSet = mutableSetOf() + + @Before + fun setup() { + ComponentsConfiguration.defaultInstance = + ComponentsConfiguration.defaultInstance.copy(enableVisibilityFixForNestedLithoView = true) + } + + @After + fun breakdown() { + ComponentsConfiguration.defaultInstance = + ComponentsConfiguration.defaultInstance.copy(enableVisibilityFixForNestedLithoView = false) + } + + @Test + fun `test visibility events for nested LithoView`() { + val testLithoView = + lithoViewRule.render { + LazyList(style = Style.height(100.sp).width(100.sp)) { + for (i in 0 until 10) { + child( + component = + VisibilityTrackingComponent(tag = i, invisibleTracking = invisibleTags)) + } + } + } + testLithoView.lithoView.subscribeComponentTreeToLifecycleProvider( + lithoVisibilityEventsControllerDelegate) + + // testing initial state + assertThat( + testLithoView.lithoView.componentTree?.lithoVisibilityEventsController?.visibilityState) + .isEqualTo(LithoVisibilityEventsController.LithoVisibilityState.HINT_VISIBLE) + assertThat(invisibleTags).isEmpty() + + // move to invisible + lithoVisibilityEventsControllerDelegate.moveToVisibilityState( + LithoVisibilityEventsController.LithoVisibilityState.HINT_INVISIBLE) + assertThat(invisibleTags).isEqualTo((0 until 10).toSet()) + + // move to visible + lithoVisibilityEventsControllerDelegate.moveToVisibilityState( + LithoVisibilityEventsController.LithoVisibilityState.HINT_VISIBLE) + assertThat(invisibleTags).isEmpty() + } +} + +class VisibilityTrackingComponent(val invisibleTracking: MutableSet, val tag: Int) : + KComponent() { + override fun ComponentScope.render(): Component { + + return Text( + text = "test", + style = + Style.height(5.sp) + .onVisible { invisibleTracking.remove(tag) } + .onInvisible { invisibleTracking.add(tag) }) + } +} diff --git a/litho-it/src/test/java/com/facebook/litho/LithoVisibilityEventsControllerTest.kt b/litho-it/src/test/java/com/facebook/litho/LithoVisibilityEventsControllerTest.kt index d6c4f3e145c..1834ee4b138 100644 --- a/litho-it/src/test/java/com/facebook/litho/LithoVisibilityEventsControllerTest.kt +++ b/litho-it/src/test/java/com/facebook/litho/LithoVisibilityEventsControllerTest.kt @@ -20,9 +20,9 @@ import android.graphics.Rect import com.facebook.litho.testing.LegacyLithoViewRule import com.facebook.litho.testing.Whitebox import com.facebook.litho.testing.exactly -import com.facebook.litho.testing.testrunner.LithoTestRunner import com.facebook.litho.widget.LayoutSpecLifecycleTester import com.facebook.litho.widget.MountSpecLifecycleTester +import com.facebook.testing.robolectric.WithTestDefaultsRunner import java.util.ArrayList import org.assertj.core.api.Assertions.assertThat import org.junit.After @@ -32,7 +32,7 @@ import org.junit.Test import org.junit.runner.RunWith import org.robolectric.annotation.LooperMode -@RunWith(LithoTestRunner::class) +@RunWith(WithTestDefaultsRunner::class) @LooperMode(LooperMode.Mode.LEGACY) class LithoVisibilityEventsControllerTest {