Skip to content

Commit

Permalink
Add ability to unmounted on detached from window
Browse files Browse the repository at this point in the history
Summary: Add ability to unmount any left over items on detached from window.

Reviewed By: adityasharat

Differential Revision: D55342976

fbshipit-source-id: f23cc5ad4cd29bdefaec109183ccb7b666edfac8
  • Loading branch information
Fabio Carballo authored and facebook-github-bot committed Mar 28, 2024
1 parent 424eb96 commit 972ba38
Show file tree
Hide file tree
Showing 5 changed files with 178 additions and 2 deletions.
4 changes: 4 additions & 0 deletions litho-core/src/main/java/com/facebook/litho/LithoView.java
Original file line number Diff line number Diff line change
Expand Up @@ -706,6 +706,10 @@ protected void onDetached() {
super.onDetached();
if (mComponentTree != null) {
mComponentTree.detach();

if (mComponentTree.getLithoConfiguration().componentsConfig.unmountOnDetachedFromWindow) {
unmountAllItems();
}
}

AccessibilityManagerCompat.removeAccessibilityStateChangeListener(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,14 @@ internal constructor(
*/
@JvmField
val shouldNotifyVisibleBoundsChangeWhenNestedLithoViewBecomesInvisible: Boolean = false,
/**
* If enabled, then the [com.facebook.litho.LithoView] will attempt to unmount any mounted
* content of the mount state when it gets detached from window.
*
* This is done to tackle and edge case where mount contents that have the same top/bottom
* boundaries of the host view are not mounted by incremental mount.
*/
@JvmField val unmountOnDetachedFromWindow: Boolean = false,
/** Whether the [ComponentTree] should be using State Reconciliation. */
@JvmField val isReconciliationEnabled: Boolean = true,
/** The handler [ComponentTree] will be used to run the pre-allocation process */
Expand Down Expand Up @@ -283,6 +291,7 @@ internal constructor(
private var shouldBuildRenderTreeInBg = baseConfig.shouldBuildRenderTreeInBg
private var enablePreAllocationSameThreadCheck = baseConfig.enablePreAllocationSameThreadCheck
private var avoidRedundantPreAllocations = baseConfig.avoidRedundantPreAllocations
private var unmountOnDetachedFromWindow = baseConfig.unmountOnDetachedFromWindow

fun shouldNotifyVisibleBoundsChangeWhenNestedLithoViewBecomesInvisible(
enabled: Boolean
Expand Down Expand Up @@ -360,6 +369,10 @@ internal constructor(
avoidRedundantPreAllocations = value
}

fun unmountOnDetachedFromWindow(unmountOnDetachedFromWindow: Boolean): Builder = also {
this.unmountOnDetachedFromWindow = unmountOnDetachedFromWindow
}

fun build(): ComponentsConfiguration {
return baseConfig.copy(
specsApiStateUpdateDuplicateDetectionEnabled =
Expand Down Expand Up @@ -390,7 +403,7 @@ internal constructor(
shouldReuseIdToPositionMap = shouldBuildRenderTreeInBg,
enablePreAllocationSameThreadCheck = enablePreAllocationSameThreadCheck,
avoidRedundantPreAllocations = avoidRedundantPreAllocations,
)
unmountOnDetachedFromWindow = unmountOnDetachedFromWindow)
}
}
}
125 changes: 125 additions & 0 deletions litho-it/src/test/java/com/facebook/litho/UnmountOnDetachTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
// (c) Meta Platforms, Inc. and affiliates. Confidential and proprietary.

package com.facebook.litho

import android.content.Context
import android.view.View
import com.facebook.litho.binders.viewBinder
import com.facebook.litho.config.ComponentsConfiguration
import com.facebook.litho.kotlin.widget.Text
import com.facebook.litho.testing.LithoViewRule
import com.facebook.litho.testing.assertj.LithoAssertions
import com.facebook.litho.testing.testrunner.LithoTestRunner
import com.facebook.rendercore.RenderUnit
import org.assertj.core.api.Assertions
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith

/**
* This test focuses on testing the behavior around
* [ComponentsConfiguration.unmountOnDetachedFromWindow].
*/
@RunWith(LithoTestRunner::class)
class UnmountOnDetachTest {

@get:Rule val lithoRule = LithoViewRule()

@Test
fun `should not unmount on detach when unmount on detach is disabled`() {
var mountCount = 0
var unmountCount = 0
val component =
UnmountOnDetachComponent(
"Mount Text", onBind = { mountCount++ }, onUnbind = { unmountCount++ })

val lithoView =
lithoRule.render(
componentTree =
ComponentTree.create(lithoRule.context)
.componentsConfiguration(
ComponentsConfiguration.defaultInstance.copy(
unmountOnDetachedFromWindow = false))
.build()) {
component
}

LithoAssertions.assertThat(lithoView).hasVisibleText("Mount Text")

lithoView.detachFromWindow()

Assertions.assertThat(mountCount).isEqualTo(1)
Assertions.assertThat(unmountCount).isEqualTo(0)
}

@Test
fun `should unmount on detach when unmount on detach is enabled`() {
var mountCount = 0
var unmountCount = 0
val component =
UnmountOnDetachComponent(
"Mount Text", onBind = { mountCount++ }, onUnbind = { unmountCount++ })

val lithoView =
lithoRule.render(
componentTree =
ComponentTree.create(lithoRule.context)
.componentsConfiguration(
ComponentsConfiguration.defaultInstance.copy(
unmountOnDetachedFromWindow = true))
.build()) {
component
}

LithoAssertions.assertThat(lithoView).hasVisibleText("Mount Text")

lithoView.detachFromWindow()

Assertions.assertThat(mountCount).isEqualTo(1)
Assertions.assertThat(unmountCount).isEqualTo(1)
}

private class UnmountOnDetachComponent(
private val name: String,
private val onBind: () -> Unit,
private val onUnbind: () -> Unit
) : KComponent() {

override fun ComponentScope.render(): Component? {
return Text(
text = name,
style =
Style.viewBinder(
RenderUnit.DelegateBinder.createDelegateBinder(
Unit,
object : RenderUnit.Binder<Any, View, Any> {
override fun shouldUpdate(
currentModel: Any,
newModel: Any,
currentLayoutData: Any?,
nextLayoutData: Any?
): Boolean = false

override fun bind(
context: Context,
content: View,
model: Any,
layoutData: Any?
): Any? {
onBind()
return Unit
}

override fun unbind(
context: Context,
content: View,
model: Any,
layoutData: Any?,
bindData: Any?
) {
onUnbind()
}
})))
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ public LithoTestRunner(final Class<?> testClass) throws InitializationError {
* configurations.
*/
private List<? extends Class<? extends LithoTestRunConfiguration>> getGlobalConfigs() {
return Arrays.asList();
return Arrays.asList(UnmountOnDetachConfiguration.class);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/*
* 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.testing.testrunner

import com.facebook.litho.config.ComponentsConfiguration
import org.junit.runners.model.FrameworkMethod

class UnmountOnDetachConfiguration : LithoTestRunConfiguration {

private val defaultInstance = ComponentsConfiguration.defaultInstance

override fun beforeTest(method: FrameworkMethod) {
ComponentsConfiguration.defaultInstance =
defaultInstance.copy(unmountOnDetachedFromWindow = true)
}

override fun afterTest(method: FrameworkMethod) {
ComponentsConfiguration.defaultInstance = defaultInstance
}
}

0 comments on commit 972ba38

Please sign in to comment.