diff options
author | Treehugger Robot <treehugger-gerrit@google.com> | 2021-10-07 00:49:38 +0000 |
---|---|---|
committer | Gerrit Code Review <noreply-gerritcodereview@google.com> | 2021-10-07 00:49:38 +0000 |
commit | bfd66e7d414e5d60b20c1e4cd4fc9594f93167a8 (patch) | |
tree | bd77f01250382576228a5c6b63eae74f360b5bb0 | |
parent | c33dce8b83296c9d712d660cde8d7bd172f57cda (diff) | |
parent | 11a26697f42c3a6d4a248eb35f52d2c26bd6d9cf (diff) | |
download | support-bfd66e7d414e5d60b20c1e4cd4fc9594f93167a8.tar.gz |
Merge "Propagate local owners as view tree owners in AndroidView." into androidx-main
4 files changed, 124 insertions, 2 deletions
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/viewinterop/AndroidViewTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/viewinterop/AndroidViewTest.kt index aba0b3637a4..9c17008648b 100644 --- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/viewinterop/AndroidViewTest.kt +++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/viewinterop/AndroidViewTest.kt @@ -37,6 +37,7 @@ import androidx.compose.foundation.layout.requiredSize import androidx.compose.foundation.layout.size import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.runtime.SideEffect import androidx.compose.runtime.compositionLocalOf import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf @@ -50,6 +51,8 @@ import androidx.compose.ui.layout.onGloballyPositioned import androidx.compose.ui.platform.ComposeView import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.LocalLayoutDirection +import androidx.compose.ui.platform.LocalLifecycleOwner +import androidx.compose.ui.platform.LocalSavedStateRegistryOwner import androidx.compose.ui.platform.ViewCompositionStrategy import androidx.compose.ui.platform.findViewTreeCompositionContext import androidx.compose.ui.platform.testTag @@ -63,6 +66,12 @@ import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.IntSize import androidx.compose.ui.unit.LayoutDirection import androidx.compose.ui.unit.dp +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.LifecycleOwner +import androidx.lifecycle.ViewTreeLifecycleOwner +import androidx.savedstate.SavedStateRegistry +import androidx.savedstate.SavedStateRegistryOwner +import androidx.savedstate.ViewTreeSavedStateRegistryOwner import androidx.test.espresso.Espresso import androidx.test.espresso.assertion.ViewAssertions.matches import androidx.test.espresso.matcher.ViewMatchers.isDisplayed @@ -377,7 +386,7 @@ class AndroidViewTest { } @Test - fun androidView_propagatesAmbientsToComposeViewChildren() { + fun androidView_propagatesLocalsToComposeViewChildren() { val ambient = compositionLocalOf { "unset" } var childComposedAmbientValue = "uncomposed" rule.setContent { @@ -437,6 +446,83 @@ class AndroidViewTest { } @Test + fun androidView_propagatesLocalLifecycleOwnerAsViewTreeOwner() { + lateinit var parentLifecycleOwner: LifecycleOwner + // We don't actually need to ever get the actual lifecycle. + val compositionLifecycleOwner = LifecycleOwner { throw UnsupportedOperationException() } + var childViewTreeLifecycleOwner: LifecycleOwner? = null + + rule.setContent { + LocalLifecycleOwner.current.also { + SideEffect { + parentLifecycleOwner = it + } + } + + CompositionLocalProvider(LocalLifecycleOwner provides compositionLifecycleOwner) { + AndroidView( + factory = { + object : FrameLayout(it) { + override fun onAttachedToWindow() { + super.onAttachedToWindow() + childViewTreeLifecycleOwner = ViewTreeLifecycleOwner.get(this) + } + } + } + ) + } + } + + rule.runOnIdle { + assertThat(childViewTreeLifecycleOwner).isSameInstanceAs(compositionLifecycleOwner) + assertThat(childViewTreeLifecycleOwner).isNotSameInstanceAs(parentLifecycleOwner) + } + } + + @Test + fun androidView_propagatesLocalSavedStateRegistryOwnerAsViewTreeOwner() { + lateinit var parentSavedStateRegistryOwner: SavedStateRegistryOwner + val compositionSavedStateRegistryOwner = object : SavedStateRegistryOwner { + // We don't actually need to ever get actual instances. + override fun getLifecycle(): Lifecycle = throw UnsupportedOperationException() + override fun getSavedStateRegistry(): SavedStateRegistry = + throw UnsupportedOperationException() + } + var childViewTreeSavedStateRegistryOwner: SavedStateRegistryOwner? = null + + rule.setContent { + LocalSavedStateRegistryOwner.current.also { + SideEffect { + parentSavedStateRegistryOwner = it + } + } + + CompositionLocalProvider( + LocalSavedStateRegistryOwner provides compositionSavedStateRegistryOwner + ) { + AndroidView( + factory = { + object : FrameLayout(it) { + override fun onAttachedToWindow() { + super.onAttachedToWindow() + childViewTreeSavedStateRegistryOwner = + ViewTreeSavedStateRegistryOwner.get(this) + } + } + } + ) + } + } + + rule.runOnIdle { + assertThat(childViewTreeSavedStateRegistryOwner) + .isSameInstanceAs(compositionSavedStateRegistryOwner) + assertThat(childViewTreeSavedStateRegistryOwner) + .isNotSameInstanceAs(parentSavedStateRegistryOwner) + } + } + + @Test fun androidView_runsFactoryExactlyOnce_afterFirstComposition() { var factoryRunCount = 0 rule.setContent { diff --git a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidCompositionLocals.android.kt b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidCompositionLocals.android.kt index a3448e05f88..ef684ae244e 100644 --- a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidCompositionLocals.android.kt +++ b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidCompositionLocals.android.kt @@ -21,8 +21,8 @@ import android.content.Context import android.content.res.Configuration import android.view.View import androidx.compose.runtime.Composable -import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.Stable import androidx.compose.runtime.compositionLocalOf import androidx.compose.runtime.getValue diff --git a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/viewinterop/AndroidView.android.kt b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/viewinterop/AndroidView.android.kt index b12612ab5ad..da5106ef457 100644 --- a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/viewinterop/AndroidView.android.kt +++ b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/viewinterop/AndroidView.android.kt @@ -37,6 +37,8 @@ import androidx.compose.ui.node.UiApplier import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.LocalLayoutDirection +import androidx.compose.ui.platform.LocalLifecycleOwner +import androidx.compose.ui.platform.LocalSavedStateRegistryOwner import androidx.compose.ui.platform.ViewRootForInspector import androidx.compose.ui.semantics.semantics import androidx.compose.ui.unit.LayoutDirection @@ -83,6 +85,14 @@ fun <T : View> AndroidView( val stateRegistry = LocalSaveableStateRegistry.current val stateKey = currentCompositeKeyHash.toString() val viewFactoryHolderRef = remember { Ref<ViewFactoryHolder<T>>() } + + // These locals are initialized from the view tree at the AndroidComposeView hosting this + // composition, but they need to be passed to this Android View so that the ViewTree*Owner + // functions return the correct owners if different local values were provided by the + // composition, e.g. by a navigation library. + val lifecycleOwner = LocalLifecycleOwner.current + val savedStateRegistryOwner = LocalSavedStateRegistryOwner.current + ComposeNode<LayoutNode, UiApplier>( factory = { val viewFactoryHolder = ViewFactoryHolder<T>(context, parentReference) @@ -96,6 +106,10 @@ fun <T : View> AndroidView( update = { set(materialized) { viewFactoryHolderRef.value!!.modifier = it } set(density) { viewFactoryHolderRef.value!!.density = it } + set(lifecycleOwner) { viewFactoryHolderRef.value!!.lifecycleOwner = it } + set(savedStateRegistryOwner) { + viewFactoryHolderRef.value!!.savedStateRegistryOwner = it + } set(update) { viewFactoryHolderRef.value!!.updateBlock = it } set(layoutDirection) { viewFactoryHolderRef.value!!.layoutDirection = when (it) { diff --git a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/viewinterop/AndroidViewHolder.android.kt b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/viewinterop/AndroidViewHolder.android.kt index 9c62aba872d..89ac1464a82 100644 --- a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/viewinterop/AndroidViewHolder.android.kt +++ b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/viewinterop/AndroidViewHolder.android.kt @@ -44,6 +44,10 @@ import androidx.compose.ui.platform.AndroidComposeView import androidx.compose.ui.platform.compositionContext import androidx.compose.ui.unit.Constraints import androidx.compose.ui.unit.Density +import androidx.lifecycle.LifecycleOwner +import androidx.lifecycle.ViewTreeLifecycleOwner +import androidx.savedstate.SavedStateRegistryOwner +import androidx.savedstate.ViewTreeSavedStateRegistryOwner import kotlin.math.roundToInt /** @@ -119,6 +123,24 @@ internal abstract class AndroidViewHolder( internal var onDensityChanged: ((Density) -> Unit)? = null + /** Sets the [ViewTreeLifecycleOwner] for this view. */ + var lifecycleOwner: LifecycleOwner? = null + set(value) { + if (value !== field) { + field = value + ViewTreeLifecycleOwner.set(this, value) + } + } + + /** Sets the [ViewTreeSavedStateRegistryOwner] for this view. */ + var savedStateRegistryOwner: SavedStateRegistryOwner? = null + set(value) { + if (value !== field) { + field = value + ViewTreeSavedStateRegistryOwner.set(this, value) + } + } + private val snapshotObserver = SnapshotStateObserver { command -> if (handler.looper === Looper.myLooper()) { command() |