From b80fc755a450194f92ebdb3ffbfdc856472e65c6 Mon Sep 17 00:00:00 2001 From: Michal Zielinski Date: Tue, 29 Oct 2024 05:34:29 -0700 Subject: [PATCH] Move ComposeComponent out of litho directory Summary: This diff moves `ComposeComponent` to compose directory because this component is internal and isn't open sourced anyways. It relies on internal APIs such as `MetaComposeView`. Reviewed By: pengj Differential Revision: D64826167 fbshipit-source-id: 443c456c3c1265b8949e650ede1a7c2bb1042191 --- litho-compose/src/main/AndroidManifest.xml | 21 - .../src/main/kotlin/com/facebook/litho/BUCK | 46 --- .../com/facebook/litho/ComposeComponent.kt | 44 --- .../com/facebook/litho/UseComposable.kt | 36 -- .../src/test/kotlin/com/facebook/litho/BUCK | 73 ---- .../facebook/litho/ComposeComponentTest.kt | 372 ------------------ 6 files changed, 592 deletions(-) delete mode 100644 litho-compose/src/main/AndroidManifest.xml delete mode 100644 litho-compose/src/main/kotlin/com/facebook/litho/BUCK delete mode 100644 litho-compose/src/main/kotlin/com/facebook/litho/ComposeComponent.kt delete mode 100644 litho-compose/src/main/kotlin/com/facebook/litho/UseComposable.kt delete mode 100644 litho-compose/src/test/kotlin/com/facebook/litho/BUCK delete mode 100644 litho-compose/src/test/kotlin/com/facebook/litho/ComposeComponentTest.kt diff --git a/litho-compose/src/main/AndroidManifest.xml b/litho-compose/src/main/AndroidManifest.xml deleted file mode 100644 index 20664c49d6..0000000000 --- a/litho-compose/src/main/AndroidManifest.xml +++ /dev/null @@ -1,21 +0,0 @@ - - - - diff --git a/litho-compose/src/main/kotlin/com/facebook/litho/BUCK b/litho-compose/src/main/kotlin/com/facebook/litho/BUCK deleted file mode 100644 index d2b6373ddb..0000000000 --- a/litho-compose/src/main/kotlin/com/facebook/litho/BUCK +++ /dev/null @@ -1,46 +0,0 @@ -# Copyright (c) Facebook, Inc. and its 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. - -load("@fbsource//xplat/pfh/FBApp_UIFrameworks_Litho_Litho:DEFS.bzl", "FBApp_UIFrameworks_Litho_Litho") -load( - "//tools/build_defs/oss:litho_defs.bzl", - "LITHO_JAVA_TARGET", - "litho_android_library", - "make_dep_path", -) - -oncall("playgroundapps") - -litho_android_library( - name = "litho-compose", - srcs = glob(["**/*.kt"]), - autoglob = False, - feature = FBApp_UIFrameworks_Litho_Litho, - k2 = True, - labels = [], - provided_deps = [ - ], - required_for_source_only_abi = True, - tests = [make_dep_path("litho-compose/src/test/kotlin/com/facebook/litho:litho-compose")], - visibility = [ - "PUBLIC", - ], - deps = [ - LITHO_JAVA_TARGET, - "//fbandroid/libraries/components/litho-widget/src/main/java/com/facebook/litho/widget:widget-bare", - "//fbandroid/libraries/rendercore/rendercore-primitive-components/compose:compose", - "//fbandroid/third-party/android/androidx/compose:runtime", - ], - exported_deps = ["//fbandroid/libraries/rendercore/rendercore-primitive-components/compose:compose"], -) diff --git a/litho-compose/src/main/kotlin/com/facebook/litho/ComposeComponent.kt b/litho-compose/src/main/kotlin/com/facebook/litho/ComposeComponent.kt deleted file mode 100644 index 0145b6839f..0000000000 --- a/litho-compose/src/main/kotlin/com/facebook/litho/ComposeComponent.kt +++ /dev/null @@ -1,44 +0,0 @@ -/* - * 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.primitive.compose.ComposableWithDeps -import com.facebook.primitive.compose.ComposePrimitive - -/** - * A Litho component that renders Jetpack Compose tree. - * - * @property composable The [ComposableWithDeps] that will be rendered by this component. Use - * [useComposable] hook to create one. - * @property contentType The content type for this component. The content recycling and updates of - * components of the same type can be performed more efficiently. It's a similar concept to - * contentType in Compose LazyColumn and RecyclerView's itemViewType. - * @property style The style to apply to the component. - */ -class ComposeComponent( - private val composable: ComposableWithDeps, - private val contentType: Any, - private val style: Style? = null, -) : PrimitiveComponent() { - override fun PrimitiveComponentScope.render(): LithoPrimitive { - return LithoPrimitive( - primitive = - ComposePrimitive( - id = createPrimitiveId(), contentType = contentType, composable = composable), - style = style) - } -} diff --git a/litho-compose/src/main/kotlin/com/facebook/litho/UseComposable.kt b/litho-compose/src/main/kotlin/com/facebook/litho/UseComposable.kt deleted file mode 100644 index 7e1be81a93..0000000000 --- a/litho-compose/src/main/kotlin/com/facebook/litho/UseComposable.kt +++ /dev/null @@ -1,36 +0,0 @@ -/* - * 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 androidx.compose.runtime.Composable -import com.facebook.litho.annotations.Hook -import com.facebook.primitive.compose.ComposableWithDeps - -/** - * Creates and caches a [ComposableWithDeps] instance to be used with [ComposeComponent]. - * - * @param deps The variables captured within the @Composable [content] lambda. Should contain any - * props or state that is used inside of [content] lambda. - * @param content The @Composable lambda that contains Compose UI. - */ -@Hook -fun ComponentScope.useComposable( - vararg deps: Any?, - content: @Composable () -> Unit -): ComposableWithDeps { - return useCached(deps) { ComposableWithDeps(deps, content) } -} diff --git a/litho-compose/src/test/kotlin/com/facebook/litho/BUCK b/litho-compose/src/test/kotlin/com/facebook/litho/BUCK deleted file mode 100644 index cd695abbee..0000000000 --- a/litho-compose/src/test/kotlin/com/facebook/litho/BUCK +++ /dev/null @@ -1,73 +0,0 @@ -# Copyright (c) 2017-present, Facebook, Inc. -# -# This source code is licensed under the Apache 2.0 license found in the -# LICENSE file in the root directory of this source tree. -load("@fbsource//tools/build_defs/android:litho_jni.bzl", "LITHO_JNI_DEPS") -load( - "//tools/build_defs/oss:litho_defs.bzl", - "LITHO_ANDROIDSUPPORT_TESTING_JUNIT_TARGET", - "LITHO_ASSERTJ_TARGET", - "LITHO_BUILD_CONFIG_TARGET", - "LITHO_COMPOSE_TARGET", - "LITHO_JAVA_TARGET", - "LITHO_JUNIT_TARGET", - "LITHO_MOCKITO_KOTLIN_V2_TARGET", - "LITHO_RENDERCORE_TARGET", - "LITHO_RENDERCORE_TESTING_TARGET", - "LITHO_ROBOLECTRIC_V4_TARGET", - "LITHO_SOLOADER_TARGET", - "LITHO_TESTING_ASSERTJ_TARGET", - "LITHO_TESTING_CORE_TARGET", - "LITHO_TESTING_VIEWTREE_TARGET", - "LITHO_WIDGET_KOTLIN_COLLECTION_TARGET", - "LITHO_WIDGET_KOTLIN_TARGET", - "LITHO_YOGA_TARGET", - "litho_robolectric4_test", - "make_dep_path", -) - -oncall("components_for_android") - -litho_robolectric4_test( - name = "litho-compose", - srcs = glob(["**/*.kt"]), - contacts = ["oncall+components_for_android@xmail.facebook.com"], - fork_mode = "per_test", - jni_deps = LITHO_JNI_DEPS, - language = "KOTLIN", - metadata = { - "buck.cfg_modifiers": ["fbsource//tools/build_defs/android/compose:compose-preview-mode-enabled"], - }, - provided_deps = [ - LITHO_ROBOLECTRIC_V4_TARGET, - ], - source = "8", - target = "8", - target_sdk_levels = ["33"], - deps = [ - LITHO_TESTING_ASSERTJ_TARGET, - LITHO_ANDROIDSUPPORT_TESTING_JUNIT_TARGET, - LITHO_ASSERTJ_TARGET, - LITHO_COMPOSE_TARGET, - LITHO_JUNIT_TARGET, - LITHO_JAVA_TARGET, - LITHO_MOCKITO_KOTLIN_V2_TARGET, - LITHO_RENDERCORE_TESTING_TARGET, - LITHO_RENDERCORE_TARGET, - LITHO_BUILD_CONFIG_TARGET, - LITHO_TESTING_CORE_TARGET, - LITHO_TESTING_VIEWTREE_TARGET, - LITHO_WIDGET_KOTLIN_COLLECTION_TARGET, - LITHO_WIDGET_KOTLIN_TARGET, - LITHO_SOLOADER_TARGET, - LITHO_YOGA_TARGET, - make_dep_path("litho-testing/src/main/java/com/facebook/litho/testing:testing"), - make_dep_path("litho-testing/src/main/java/com/facebook/litho/testing/helper:helper"), - make_dep_path("litho-testing/src/main/java/com/facebook/litho/testing/testrunner:testrunner"), - "//fbandroid/libraries/compose/view:view", - "//fbandroid/libraries/rendercore/rendercore-primitive-components/compose:compose", - "//fbandroid/third-party/android/androidx/compose:foundation", - "//fbandroid/third-party/android/androidx/compose:runtime", - "//fbandroid/third-party/android/androidx/compose:ui", - ], -) diff --git a/litho-compose/src/test/kotlin/com/facebook/litho/ComposeComponentTest.kt b/litho-compose/src/test/kotlin/com/facebook/litho/ComposeComponentTest.kt deleted file mode 100644 index 79aaa50d9f..0000000000 --- a/litho-compose/src/test/kotlin/com/facebook/litho/ComposeComponentTest.kt +++ /dev/null @@ -1,372 +0,0 @@ -/* - * 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.view.ViewGroup -import android.widget.FrameLayout -import androidx.activity.ComponentActivity -import androidx.compose.foundation.layout.Box -import androidx.compose.runtime.DisposableEffect -import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.RememberObserver -import androidx.compose.runtime.SideEffect -import androidx.compose.runtime.remember -import com.facebook.compose.view.MetaComposeView -import com.facebook.litho.testing.LithoTestRule -import com.facebook.litho.testing.testrunner.LithoTestRunner -import org.assertj.core.api.Assertions.assertThat -import org.junit.Before -import org.junit.Rule -import org.junit.Test -import org.junit.runner.RunWith -import org.robolectric.Robolectric -import org.robolectric.annotation.LooperMode - -@LooperMode(LooperMode.Mode.LEGACY) -@RunWith(LithoTestRunner::class) -class ComposeComponentTest { - - @Rule @JvmField val lithoViewRule = LithoTestRule() - - lateinit var lithoView: LithoView - - @Before - fun setUp() { - val activity = - Robolectric.buildActivity(ComponentActivity::class.java).setup().get().apply { - setContentView(FrameLayout(this)) - } - lithoViewRule.context = ComponentContext(activity) - lithoView = LithoView(lithoViewRule.context) - activity.findViewById(android.R.id.content).addView(lithoView) - } - - @Test - fun `should render compose component`() { - var rendered = false - class TestComponent : KComponent() { - override fun ComponentScope.render(): Component { - return ComposeComponent( - composable = useComposable(Unit) { Box { rendered = true } }, - contentType = TEST_COMPONENT_CONTENT_TYPE) - } - } - - val testView = - lithoViewRule.render(lithoView = lithoView, widthPx = 100, heightPx = 100) { - TestComponent() - } - - assertThat(rendered).isTrue() - - assertThat(testView.lithoView.getChildAt(0)).isInstanceOf(MetaComposeView::class.java) - val composeView = testView.lithoView.getChildAt(0) as MetaComposeView - - assertThat(composeView.hasComposition).isTrue() - - testView.lithoView.unmountAllItems() - - assertThat(composeView.hasComposition).isFalse() - } - - @Test - fun `should update compose component with new content only when deps are changed`() { - var boxRendered = false - var rowRendered = false - class TestComponent(private val useBoxComposable: Boolean) : KComponent() { - override fun ComponentScope.render(): Component { - val composable = - useComposable(useBoxComposable) { - if (useBoxComposable) { - Box { boxRendered = true } - } else { - Row { rowRendered = true } - } - } - - return ComposeComponent(composable = composable, contentType = TEST_COMPONENT_CONTENT_TYPE) - } - } - - // Initial render - var testView = - lithoViewRule.render(lithoView = lithoView, widthPx = 100, heightPx = 100) { - TestComponent(useBoxComposable = true) - } - - assertThat(boxRendered).isTrue() - assertThat(rowRendered).isFalse() - - // Update with changed deps - boxRendered = false - rowRendered = false - - testView = - lithoViewRule.render(lithoView = lithoView, widthPx = 100, heightPx = 100) { - TestComponent(useBoxComposable = false) - } - - // deps are changed so box is not rendered and row is rendered - assertThat(boxRendered).isFalse() - assertThat(rowRendered).isTrue() - - // Update without changing deps - boxRendered = false - rowRendered = false - - testView = - lithoViewRule.render(lithoView = lithoView, widthPx = 100, heightPx = 100) { - TestComponent(useBoxComposable = false) - } - - assertThat(boxRendered).isFalse() - // deps aren't changed so the Row shouldn't be re-rendered - assertThat(rowRendered).isFalse() - - testView.lithoView.unmountAllItems() - } - - @Test - fun `should run effects cleanup when component is removed from the hierarchy`() { - var launchedEffectRan = false - var sideEffectRan = false - var disposableEffectRan = false - var disposableEffectDisposed = false - var onRememberRan = false - var onForgottenRan = false - class TestComponent : KComponent() { - override fun ComponentScope.render(): Component { - return ComposeComponent( - composable = - useComposable(Unit) { - LaunchedEffect(Unit) { launchedEffectRan = true } - SideEffect { sideEffectRan = true } - DisposableEffect(Unit) { - disposableEffectRan = true - onDispose { disposableEffectDisposed = true } - } - val remembered = remember { - object : RememberObserver { - override fun onRemembered() { - onRememberRan = true - } - - override fun onForgotten() { - onForgottenRan = true - } - - override fun onAbandoned() = Unit - } - } - }, - contentType = TEST_COMPONENT_CONTENT_TYPE) - } - } - - val testView = - lithoViewRule.render(lithoView = lithoView, widthPx = 100, heightPx = 100) { - TestComponent() - } - - assertThat(launchedEffectRan).isTrue() - assertThat(sideEffectRan).isTrue() - - assertThat(disposableEffectRan).isTrue() - assertThat(disposableEffectDisposed).isFalse() - - assertThat(onRememberRan).isTrue() - assertThat(onForgottenRan).isFalse() - - testView.lithoView.unmountAllItems() - - assertThat(launchedEffectRan).isTrue() - assertThat(sideEffectRan).isTrue() - - assertThat(disposableEffectRan).isTrue() - assertThat(disposableEffectDisposed).isTrue() - - assertThat(onRememberRan).isTrue() - assertThat(onForgottenRan).isTrue() - } - - @Test - fun `should run effects cleanup when component is updated with new composable content`() { - var launchedEffectRan = false - var sideEffectRan = false - var disposableEffectRan = false - var disposableEffectDisposed = false - var onRememberRan = false - var onForgottenRan = false - class TestComponent(private val useComposableWithEffects: Boolean) : KComponent() { - override fun ComponentScope.render(): Component { - val composable = - useComposable(useComposableWithEffects) { - if (useComposableWithEffects) { - LaunchedEffect(Unit) { launchedEffectRan = true } - SideEffect { sideEffectRan = true } - DisposableEffect(Unit) { - disposableEffectRan = true - onDispose { disposableEffectDisposed = true } - } - val remembered = remember { - object : RememberObserver { - override fun onRemembered() { - onRememberRan = true - } - - override fun onForgotten() { - onForgottenRan = true - } - - override fun onAbandoned() = Unit - } - } - } else { - Row {} - } - } - - return ComposeComponent(composable = composable, contentType = TEST_COMPONENT_CONTENT_TYPE) - } - } - - // Initial render - var testView = - lithoViewRule.render(lithoView = lithoView, widthPx = 100, heightPx = 100) { - TestComponent(useComposableWithEffects = true) - } - - assertThat(launchedEffectRan).isTrue() - assertThat(sideEffectRan).isTrue() - - assertThat(disposableEffectRan).isTrue() - assertThat(disposableEffectDisposed).isFalse() - - assertThat(onRememberRan).isTrue() - assertThat(onForgottenRan).isFalse() - - // Update with changed deps - testView = - lithoViewRule.render(lithoView = lithoView, widthPx = 100, heightPx = 100) { - TestComponent(useComposableWithEffects = false) - } - - assertThat(launchedEffectRan).isTrue() - assertThat(sideEffectRan).isTrue() - - assertThat(disposableEffectRan).isTrue() - assertThat(disposableEffectDisposed).isTrue() - - assertThat(onRememberRan).isTrue() - assertThat(onForgottenRan).isTrue() - - testView.lithoView.unmountAllItems() - } - - @Test - fun `should use the same pool for components with the same content types`() { - class TestComponent(private val contentType: Any) : KComponent() { - override fun ComponentScope.render(): Component { - return ComposeComponent( - composable = useComposable(Unit) { Box {} }, contentType = contentType) - } - } - - val testViewFirst = - lithoViewRule.render(lithoView = lithoView, widthPx = 100, heightPx = 100) { - TestComponent(contentType = TEST_COMPONENT_CONTENT_TYPE) - } - - assertThat(testViewFirst.lithoView.getChildAt(0)).isInstanceOf(MetaComposeView::class.java) - val composeViewFirst = testViewFirst.lithoView.getChildAt(0) as MetaComposeView - - // unmount and return the view to the pool - testViewFirst.lithoView.unmountAllItems() - - // render component with the same content type - val testViewSecond = - lithoViewRule.render(lithoView = lithoView, widthPx = 100, heightPx = 100) { - TestComponent(contentType = TEST_COMPONENT_CONTENT_TYPE) - } - - assertThat(testViewSecond.lithoView.getChildAt(0)).isInstanceOf(MetaComposeView::class.java) - val composeViewSecond = testViewSecond.lithoView.getChildAt(0) as MetaComposeView - - // instances should be the same because content type is the same - assertThat(composeViewFirst).isSameAs(composeViewSecond) - - testViewSecond.lithoView.unmountAllItems() - } - - @Test - fun `should use separate pools for components with different content types`() { - class TestComponent(private val contentType: Any) : KComponent() { - override fun ComponentScope.render(): Component { - return ComposeComponent( - composable = useComposable(Unit) { Box {} }, contentType = contentType) - } - } - - val testViewFirst = - lithoViewRule.render(lithoView = lithoView, widthPx = 100, heightPx = 100) { - TestComponent(contentType = TEST_COMPONENT_CONTENT_TYPE) - } - - assertThat(testViewFirst.lithoView.getChildAt(0)).isInstanceOf(MetaComposeView::class.java) - val composeViewFirst = testViewFirst.lithoView.getChildAt(0) as MetaComposeView - - // unmount and return the view to the pool - testViewFirst.lithoView.unmountAllItems() - - // render component with a different content type - val testViewSecond = - lithoViewRule.render(lithoView = lithoView, widthPx = 100, heightPx = 100) { - TestComponent(contentType = OTHER_TEST_COMPONENT_CONTENT_TYPE) - } - - assertThat(testViewSecond.lithoView.getChildAt(0)).isInstanceOf(MetaComposeView::class.java) - val composeViewSecond = testViewSecond.lithoView.getChildAt(0) as MetaComposeView - - // instances should be different because content type is different - assertThat(composeViewFirst).isNotSameAs(composeViewSecond) - - testViewSecond.lithoView.unmountAllItems() - } - - @Test - fun `should not crash when LithoView isn't attached to the window`() { - val unattachedLithoView = LithoView(lithoViewRule.context) - - class TestComponent : KComponent() { - override fun ComponentScope.render(): Component { - return ComposeComponent( - composable = useComposable(Unit) { Box {} }, contentType = TEST_COMPONENT_CONTENT_TYPE) - } - } - - // This would crash without CompositionContext fix - val testView = - lithoViewRule.render(lithoView = unattachedLithoView, widthPx = 100, heightPx = 100) { - TestComponent() - } - - testView.lithoView.unmountAllItems() - } -} - -private val TEST_COMPONENT_CONTENT_TYPE = "com.facebook.litho.TestComponent" -private val OTHER_TEST_COMPONENT_CONTENT_TYPE = "com.facebook.litho.OtherTestComponent"