Skip to content

Commit b40a30a

Browse files
committed
Better use of Compatible for ease of container uniquing.
1 parent d2ff0ad commit b40a30a

File tree

7 files changed

+33
-23
lines changed

7 files changed

+33
-23
lines changed

samples/containers/android/src/main/java/com/squareup/sample/container/overviewdetail/OverviewDetailContainer.kt

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,12 @@ class OverviewDetailContainer(view: View) : LayoutRunner<OverviewDetailScreen> {
5454
rendering.selectDefault!!.invoke()
5555
} else {
5656
val overviewViewEnvironment = viewEnvironment + (OverviewDetailConfig to Overview)
57-
overviewStub!!.update(Named(rendering.overviewRendering, "overview"), overviewViewEnvironment)
57+
58+
// Without this name, the two BackStackScreen containers will try
59+
// sign up with SavedStateRegistry with the same id, and crash.
60+
val overviewRendering = Named(rendering.overviewRendering, "Overview")
61+
overviewStub!!.update(overviewRendering, overviewViewEnvironment)
62+
5863
rendering.detailRendering
5964
?.let { detail ->
6065
detailStub!!.actual.visibility = VISIBLE

workflow-ui/container-android/src/main/java/com/squareup/workflow1/ui/backstack/ViewStateCache.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ internal constructor(
111111
}
112112

113113
// Create and install a new SavedStateRegistryOwner for the new view and wire it up to the
114-
// view's lifecycle. We need to do this even if the StateRegistryHolder hasn't been restored
114+
// view's lifecycle. We need to do this even if the StateRegistryAggregator hasn't been restored
115115
// yet, the view can ask for it as soon as it's attached.
116116
val registryOwner = KeyedStateRegistryOwner.installAsSavedStateRegistryOwnerOn(newView, newKey)
117117
currentOwner = registryOwner

workflow-ui/container-android/src/main/java/com/squareup/workflow1/ui/modal/ModalContainer.kt

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import android.view.ViewGroup.LayoutParams.MATCH_PARENT
1313
import android.widget.FrameLayout
1414
import androidx.lifecycle.DefaultLifecycleObserver
1515
import androidx.lifecycle.Lifecycle
16+
import androidx.lifecycle.Lifecycle.State.INITIALIZED
1617
import androidx.lifecycle.LifecycleOwner
1718
import com.squareup.workflow1.ui.Compatible
1819
import com.squareup.workflow1.ui.ViewEnvironment
@@ -65,9 +66,13 @@ public abstract class ModalContainer<ModalRenderingT : Any> @JvmOverloads constr
6566
},
6667
onRestored = { aggregator ->
6768
dialogs.forEach { ref ->
68-
ref.stateRegistryOwner.let {
69-
aggregator.restoreRegistryControllerIfReady(it.key, it.controller)
70-
}
69+
// We're only allowed to restore from an INITIALIZED state, but this callback can also be
70+
// invoked while the owner is already CREATED.
71+
// https://github.com/square/workflow-kotlin/issues/570
72+
ref.stateRegistryOwner.takeIf { it.lifecycle.currentState == INITIALIZED }
73+
?.let {
74+
aggregator.restoreRegistryControllerIfReady(it.key, it.controller)
75+
}
7176
}
7277
// If any keys were not in the list of dialogs, we don't want to restore them ever.
7378
aggregator.pruneKeys(keysToKeep = emptyList())
@@ -100,7 +105,7 @@ public abstract class ModalContainer<ModalRenderingT : Any> @JvmOverloads constr
100105
// don't clash with other layers.
101106
ref.stateRegistryOwner = KeyedStateRegistryOwner.installAsSavedStateRegistryOwnerOn(
102107
view = dialogView,
103-
key = "${this.javaClass.name}[$i]"
108+
key = Compatible.keyFor(newScreen, i.toString())
104109
)
105110

106111
dialogView.addOnAttachStateChangeListener(
@@ -276,7 +281,7 @@ public abstract class ModalContainer<ModalRenderingT : Any> @JvmOverloads constr
276281

277282
constructor(source: Parcel) : super(source) {
278283
@Suppress("UNCHECKED_CAST")
279-
this.dialogBundles = mutableListOf<KeyAndBundle>().apply {
284+
dialogBundles = mutableListOf<KeyAndBundle>().apply {
280285
source.readTypedList(this, KeyAndBundle)
281286
}
282287
}

workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/androidx/KeyedStateRegistryOwner.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ public class KeyedStateRegistryOwner private constructor(
4343
key: String
4444
): KeyedStateRegistryOwner {
4545
val lifecycleOwner = checkNotNull(WorkflowLifecycleOwner.get(view)) {
46-
"Expected back stack container view to set a WorkflowLifecycleOwner on its immediate " +
46+
"Expected container view to set a WorkflowLifecycleOwner on its immediate " +
4747
"child views."
4848
}
4949
val registryOwner = KeyedStateRegistryOwner(key, lifecycleOwner)

workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/androidx/StateRegistryAggregator.kt

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -82,9 +82,9 @@ public class StateRegistryAggregator(
8282

8383
// These properties are guaranteed to be non-null because this observer is only registered
8484
// while attached, and these properties are always non-null while attached.
85-
val restoredState = parentRegistryOwner!!.savedStateRegistry.takeIf { it.isRestored }
86-
?.consumeRestoredStateForKey(parentKey!!)
87-
restoreFromBundle(restoredState)
85+
parentRegistryOwner!!.savedStateRegistry.takeIf { it.isRestored }?.let {
86+
restoreFromBundle(it.consumeRestoredStateForKey(parentKey!!))
87+
}
8888
}
8989
}
9090

@@ -127,9 +127,10 @@ public class StateRegistryAggregator(
127127
parentRegistry.registerSavedStateProvider(key, ::saveToBundle)
128128
} catch (e: IllegalArgumentException) {
129129
throw IllegalArgumentException(
130-
"Error registering StateRegistryHolder as SavedStateProvider with key \"$key\" on parent " +
131-
"SavedStateRegistryOwner $parentOwner.\n" +
132-
"You might need to wrap your container screen with a Named rendering.",
130+
"Error registering SavedStateProvider: key \"$key\" is already in " +
131+
"use on parent SavedStateRegistryOwner $parentOwner.\n" +
132+
"Give your container screen a unique Compatible.compatibilityKey, perhaps " +
133+
"by wrapping it with Named.",
133134
e
134135
)
135136
}

workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/androidx/WorkflowAndroidXSupport.kt

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import androidx.lifecycle.ViewTreeLifecycleOwner
99
import androidx.savedstate.SavedStateRegistry
1010
import androidx.savedstate.SavedStateRegistryOwner
1111
import androidx.savedstate.ViewTreeSavedStateRegistryOwner
12+
import com.squareup.workflow1.ui.Compatible
1213
import com.squareup.workflow1.ui.Named
1314
import com.squareup.workflow1.ui.WorkflowUiExperimentalApi
1415
import com.squareup.workflow1.ui.getRendering
@@ -77,11 +78,9 @@ public object WorkflowAndroidXSupport {
7778
*/
7879
@OptIn(WorkflowUiExperimentalApi::class)
7980
public fun createStateRegistryKeyForContainer(containerView: View): String {
80-
val namedKeyOrNull = run {
81-
val rendering = containerView.getRendering<Any>() as? Named<*>
82-
rendering?.compatibilityKey
83-
}
84-
val nameSuffix = namedKeyOrNull?.let { "-$it" } ?: ""
81+
val nameSuffix = containerView.getRendering<Any>()
82+
?.let { Compatible.keyFor(it) }?.let { "-$it" }
83+
?: ""
8584
val idSuffix = if (containerView.id == FrameLayout.NO_ID) "" else "-${containerView.id}"
8685
return containerView.javaClass.name + nameSuffix + idSuffix
8786
}

workflow-ui/core-android/src/test/java/com/squareup/workflow1/ui/androidx/StateRegistryAggregatorTest.kt

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ internal class StateRegistryAggregatorTest {
4949
}
5050

5151
assertThat(error).hasMessageThat()
52-
.contains("Error registering StateRegistryHolder as SavedStateProvider with key \"$key\"")
52+
.contains("Error registering SavedStateProvider: key \"$key\" is already in use")
5353
}
5454

5555
@Test fun `attach observes parent lifecycle`() {
@@ -343,9 +343,9 @@ internal class StateRegistryAggregatorTest {
343343
aggregator.attachToParentRegistry("parentKey", parent)
344344

345345
parent.lifecycleRegistry.currentState = RESUMED
346-
// We used to crash here:
347-
// IllegalStateException: You can consumeRestoredStateForKey only after super.onCreate
348-
assertThat(restoreCount).isEqualTo(1)
346+
// We once crashed here:
347+
// IllegalStateException: You can consumeRestoredStateForKey only after super.onCreate
348+
assertThat(restoreCount).isEqualTo(0)
349349
}
350350

351351
/**

0 commit comments

Comments
 (0)