aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTreehugger Robot <treehugger-gerrit@google.com>2021-10-07 00:49:38 +0000
committerGerrit Code Review <noreply-gerritcodereview@google.com>2021-10-07 00:49:38 +0000
commitbfd66e7d414e5d60b20c1e4cd4fc9594f93167a8 (patch)
treebd77f01250382576228a5c6b63eae74f360b5bb0
parentc33dce8b83296c9d712d660cde8d7bd172f57cda (diff)
parent11a26697f42c3a6d4a248eb35f52d2c26bd6d9cf (diff)
downloadsupport-bfd66e7d414e5d60b20c1e4cd4fc9594f93167a8.tar.gz
Merge "Propagate local owners as view tree owners in AndroidView." into androidx-main
-rw-r--r--compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/viewinterop/AndroidViewTest.kt88
-rw-r--r--compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidCompositionLocals.android.kt2
-rw-r--r--compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/viewinterop/AndroidView.android.kt14
-rw-r--r--compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/viewinterop/AndroidViewHolder.android.kt22
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()