Skip to content

Commit f6e4da3

Browse files
committed
Introduces ViewEnvironmentKey.combine, makes ViewRegistry use it.
`ViewEnvironmentKey.combine` is called from `ViewEnvironment.plus` when both operands have keys of the same type. `ViewRegistry.Companion` is already a `ViewEnvironmentKey`, and now it implements `combine`, calling `ViewRegistry.merge`. This changes `ViewEnvironment.plus` to merge the values of `ViewRegistry` entries instead of replacing the registry on the left with the one on the right, which is the only thing we've ever actually wanted to do. This allows us to eliminate `ViewEnvironment.merge`. We have never seen a use case for completely stomping the `ViewRegistry` on the left, but have done it by accident a lot. If the need really does arise, we can add a `ViewEnvironment.minus` operator. Also eliminates the `ViewEnvironmentKey()` factory function, which was just silly.
1 parent a6fa11c commit f6e4da3

File tree

13 files changed

+96
-110
lines changed

13 files changed

+96
-110
lines changed

gradle.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ android.useAndroidX=true
88
systemProp.org.gradle.internal.publish.checksums.insecure=true
99

1010
GROUP=com.squareup.workflow1
11-
VERSION_NAME=1.8.0-uiUpdate03-SNAPSHOT
11+
VERSION_NAME=1.8.0-uiUpdate04-SNAPSHOT
1212

1313
POM_DESCRIPTION=Square Workflow
1414

workflow-ui/core-android/api/core-android.api

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -199,7 +199,12 @@ public abstract interface class com/squareup/workflow1/ui/ScreenViewHolder {
199199
}
200200

201201
public final class com/squareup/workflow1/ui/ScreenViewHolder$Companion {
202-
public final fun getShowing ()Lcom/squareup/workflow1/ui/ViewEnvironmentKey;
202+
}
203+
204+
public final class com/squareup/workflow1/ui/ScreenViewHolder$Companion$Showing : com/squareup/workflow1/ui/ViewEnvironmentKey {
205+
public static final field INSTANCE Lcom/squareup/workflow1/ui/ScreenViewHolder$Companion$Showing;
206+
public fun getDefault ()Lcom/squareup/workflow1/ui/Screen;
207+
public synthetic fun getDefault ()Ljava/lang/Object;
203208
}
204209

205210
public final class com/squareup/workflow1/ui/ScreenViewHolder$Companion$ShowingNothing : com/squareup/workflow1/ui/Screen {

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,9 @@ public interface ScreenViewHolder<in ScreenT : Screen> {
4444
* Provides access to the [Screen] instance most recently shown in a [ScreenViewHolder]'s
4545
* [view] via [show]. Call [showing] for more convenient access.
4646
*/
47-
public val Showing: ViewEnvironmentKey<Screen> = ViewEnvironmentKey { ShowingNothing }
47+
public object Showing : ViewEnvironmentKey<Screen>(Screen::class) {
48+
override val default: Screen = ShowingNothing
49+
}
4850
}
4951
}
5052

workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/container/BackStackContainer.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import com.squareup.workflow1.ui.Compatible.Companion.keyFor
2020
import com.squareup.workflow1.ui.NamedScreen
2121
import com.squareup.workflow1.ui.R
2222
import com.squareup.workflow1.ui.ScreenViewHolder
23+
import com.squareup.workflow1.ui.ScreenViewHolder.Companion.Showing
2324
import com.squareup.workflow1.ui.ViewEnvironment
2425
import com.squareup.workflow1.ui.WorkflowUiExperimentalApi
2526
import com.squareup.workflow1.ui.androidx.WorkflowAndroidXSupport.stateRegistryOwnerFromViewTreeOrContext
@@ -65,7 +66,7 @@ public open class BackStackContainer @JvmOverloads constructor(
6566
newRendering: BackStackScreen<*>,
6667
newViewEnvironment: ViewEnvironment
6768
) {
68-
savedStateParentKey = keyFor(newViewEnvironment[ScreenViewHolder.Showing])
69+
savedStateParentKey = keyFor(newViewEnvironment[Showing])
6970

7071
val config = if (newRendering.backStack.isEmpty()) First else Other
7172
val environment = newViewEnvironment + config

workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/container/BodyAndModalsContainer.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import com.squareup.workflow1.ui.Compatible.Companion.keyFor
1717
import com.squareup.workflow1.ui.R
1818
import com.squareup.workflow1.ui.ScreenViewFactory
1919
import com.squareup.workflow1.ui.ScreenViewHolder
20+
import com.squareup.workflow1.ui.ScreenViewHolder.Companion.Showing
2021
import com.squareup.workflow1.ui.ViewEnvironment
2122
import com.squareup.workflow1.ui.WorkflowUiExperimentalApi
2223
import com.squareup.workflow1.ui.WorkflowViewStub
@@ -82,7 +83,7 @@ internal class BodyAndModalsContainer @JvmOverloads constructor(
8283
newScreen: BodyAndModalsScreen<*, *>,
8384
viewEnvironment: ViewEnvironment
8485
) {
85-
savedStateParentKey = keyFor(viewEnvironment[ScreenViewHolder.Showing])
86+
savedStateParentKey = keyFor(viewEnvironment[Showing])
8687

8788
val showingModals = newScreen.modals.isNotEmpty()
8889

workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/container/EnvironmentScreenLegacyViewFactory.kt

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,13 @@ package com.squareup.workflow1.ui.container
55
import com.squareup.workflow1.ui.DecorativeViewFactory
66
import com.squareup.workflow1.ui.ViewFactory
77
import com.squareup.workflow1.ui.WorkflowUiExperimentalApi
8-
import com.squareup.workflow1.ui.merge
98

109
@Suppress("DEPRECATION")
1110
@WorkflowUiExperimentalApi
1211
internal object EnvironmentScreenLegacyViewFactory : ViewFactory<EnvironmentScreen<*>>
1312
by DecorativeViewFactory(
1413
type = EnvironmentScreen::class,
1514
map = { environmentScreen, inheritedEnvironment ->
16-
Pair(environmentScreen.wrapped, environmentScreen.environment merge inheritedEnvironment)
15+
Pair(environmentScreen.wrapped, environmentScreen.environment + inheritedEnvironment)
1716
}
1817
)

workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/container/EnvironmentScreenViewFactory.kt

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,21 +4,20 @@ import com.squareup.workflow1.ui.Screen
44
import com.squareup.workflow1.ui.ScreenViewFactory
55
import com.squareup.workflow1.ui.ScreenViewFactory.Companion.fromCode
66
import com.squareup.workflow1.ui.WorkflowUiExperimentalApi
7-
import com.squareup.workflow1.ui.merge
87
import com.squareup.workflow1.ui.toUnwrappingViewFactory
98
import com.squareup.workflow1.ui.toViewFactory
109

1110
@WorkflowUiExperimentalApi
1211
internal fun <WrappedT : Screen> EnvironmentScreenViewFactory():
1312
ScreenViewFactory<EnvironmentScreen<WrappedT>> {
1413
return fromCode { initialEnvScreen, initialEnvironment, context, container ->
15-
val mergedInitialEnvironment = initialEnvironment merge initialEnvScreen.environment
14+
val mergedInitialEnvironment = initialEnvironment + initialEnvScreen.environment
1615

1716
initialEnvScreen.wrapped.toViewFactory(mergedInitialEnvironment)
1817
.toUnwrappingViewFactory<EnvironmentScreen<WrappedT>, WrappedT>(
1918
unwrap = { it.wrapped },
2019
showWrapperScreen = { _, envScreen, environment, showUnwrapped ->
21-
showUnwrapped(envScreen.wrapped, environment merge envScreen.environment)
20+
showUnwrapped(envScreen.wrapped, environment + envScreen.environment)
2221
}
2322
)
2423
.buildView(initialEnvScreen, mergedInitialEnvironment, context, container)

workflow-ui/core-common/api/core-common.api

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -102,14 +102,11 @@ public final class com/squareup/workflow1/ui/ViewEnvironment$Companion {
102102

103103
public abstract class com/squareup/workflow1/ui/ViewEnvironmentKey {
104104
public fun <init> (Lkotlin/reflect/KClass;)V
105+
public fun combine (Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
105106
public final fun equals (Ljava/lang/Object;)Z
106107
public abstract fun getDefault ()Ljava/lang/Object;
107108
public final fun hashCode ()I
108-
public fun toString ()Ljava/lang/String;
109-
}
110-
111-
public final class com/squareup/workflow1/ui/ViewEnvironmentKt {
112-
public static final synthetic fun ViewEnvironmentKey (Lkotlin/jvm/functions/Function0;)Lcom/squareup/workflow1/ui/ViewEnvironmentKey;
109+
public final fun toString ()Ljava/lang/String;
113110
}
114111

115112
public abstract interface class com/squareup/workflow1/ui/ViewRegistry {
@@ -119,6 +116,8 @@ public abstract interface class com/squareup/workflow1/ui/ViewRegistry {
119116
}
120117

121118
public final class com/squareup/workflow1/ui/ViewRegistry$Companion : com/squareup/workflow1/ui/ViewEnvironmentKey {
119+
public fun combine (Lcom/squareup/workflow1/ui/ViewRegistry;Lcom/squareup/workflow1/ui/ViewRegistry;)Lcom/squareup/workflow1/ui/ViewRegistry;
120+
public synthetic fun combine (Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
122121
public fun getDefault ()Lcom/squareup/workflow1/ui/ViewRegistry;
123122
public synthetic fun getDefault ()Ljava/lang/Object;
124123
}
@@ -131,8 +130,6 @@ public final class com/squareup/workflow1/ui/ViewRegistryKt {
131130
public static final fun ViewRegistry ()Lcom/squareup/workflow1/ui/ViewRegistry;
132131
public static final fun ViewRegistry ([Lcom/squareup/workflow1/ui/ViewRegistry$Entry;)Lcom/squareup/workflow1/ui/ViewRegistry;
133132
public static final synthetic fun get (Lcom/squareup/workflow1/ui/ViewRegistry;Lkotlin/reflect/KClass;)Lcom/squareup/workflow1/ui/ViewRegistry$Entry;
134-
public static final fun merge (Lcom/squareup/workflow1/ui/ViewEnvironment;Lcom/squareup/workflow1/ui/ViewEnvironment;)Lcom/squareup/workflow1/ui/ViewEnvironment;
135-
public static final fun merge (Lcom/squareup/workflow1/ui/ViewEnvironment;Lcom/squareup/workflow1/ui/ViewRegistry;)Lcom/squareup/workflow1/ui/ViewEnvironment;
136133
public static final fun merge (Lcom/squareup/workflow1/ui/ViewRegistry;Lcom/squareup/workflow1/ui/ViewRegistry;)Lcom/squareup/workflow1/ui/ViewRegistry;
137134
public static final fun plus (Lcom/squareup/workflow1/ui/ViewEnvironment;Lcom/squareup/workflow1/ui/ViewRegistry;)Lcom/squareup/workflow1/ui/ViewEnvironment;
138135
public static final fun plus (Lcom/squareup/workflow1/ui/ViewRegistry;Lcom/squareup/workflow1/ui/ViewRegistry$Entry;)Lcom/squareup/workflow1/ui/ViewRegistry;

workflow-ui/core-common/src/main/java/com/squareup/workflow1/ui/ViewEnvironment.kt

Lines changed: 27 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -19,19 +19,27 @@ public class ViewEnvironment
1919
constructor(
2020
public val map: Map<ViewEnvironmentKey<*>, Any> = emptyMap()
2121
) {
22-
@Suppress("UNCHECKED_CAST")
23-
public operator fun <T : Any> get(key: ViewEnvironmentKey<T>): T = map[key] as? T ?: key.default
22+
public operator fun <T : Any> get(key: ViewEnvironmentKey<T>): T = getOrNull(key) ?: key.default
2423

25-
@Suppress("DEPRECATION")
26-
public operator fun <T : Any> plus(pair: Pair<ViewEnvironmentKey<T>, T>): ViewEnvironment =
27-
ViewEnvironment(map + pair)
24+
public operator fun <T : Any> plus(pair: Pair<ViewEnvironmentKey<T>, T>): ViewEnvironment {
25+
val (newKey, newValue) = pair
26+
val newPair = getOrNull(newKey)?.let { newKey to newKey.combine(it, newValue) } ?: pair
27+
@Suppress("DEPRECATION")
28+
return ViewEnvironment(map + newPair)
29+
}
2830

2931
@Suppress("DEPRECATION")
3032
public operator fun plus(other: ViewEnvironment): ViewEnvironment {
3133
if (this == other) return this
3234
if (other.map.isEmpty()) return this
3335
if (this.map.isEmpty()) return other
34-
return ViewEnvironment(map + other.map)
36+
val newMap = map.toMutableMap()
37+
other.map.entries.forEach { (key, value) ->
38+
@Suppress("UNCHECKED_CAST")
39+
newMap[key] =
40+
getOrNull(key as ViewEnvironmentKey<Any>)?.let { key.combine(it, value) } ?: value
41+
}
42+
return ViewEnvironment(newMap)
3543
}
3644

3745
override fun toString(): String = "ViewEnvironment($map)"
@@ -41,6 +49,9 @@ constructor(
4149

4250
override fun hashCode(): Int = map.hashCode()
4351

52+
@Suppress("UNCHECKED_CAST")
53+
private fun <T : Any> getOrNull(key: ViewEnvironmentKey<T>): T? = map[key] as? T
54+
4455
public companion object {
4556
@Suppress("DEPRECATION")
4657
public val EMPTY: ViewEnvironment = ViewEnvironment()
@@ -57,6 +68,15 @@ public abstract class ViewEnvironmentKey<T : Any>(
5768
) {
5869
public abstract val default: T
5970

71+
/**
72+
* Applied from [ViewEnvironment.plus] when the receiving environment already contains
73+
* a value for this key. The default implementation replaces [left] with [right].
74+
*/
75+
public open fun combine(
76+
left: T,
77+
right: T
78+
): T = right
79+
6080
final override fun equals(other: Any?): Boolean = when {
6181
this === other -> true
6282
other != null && this::class != other::class -> false
@@ -65,17 +85,7 @@ public abstract class ViewEnvironmentKey<T : Any>(
6585

6686
final override fun hashCode(): Int = type.hashCode()
6787

68-
override fun toString(): String {
88+
final override fun toString(): String {
6989
return "${this::class.simpleName}(${type.simpleName})"
7090
}
7191
}
72-
73-
@WorkflowUiExperimentalApi
74-
public inline fun <reified T : Any> ViewEnvironmentKey(
75-
crossinline produceDefault: () -> T,
76-
): ViewEnvironmentKey<T> {
77-
return object : ViewEnvironmentKey<T>(T::class) {
78-
override val default: T
79-
get() = produceDefault()
80-
}
81-
}

workflow-ui/core-common/src/main/java/com/squareup/workflow1/ui/ViewRegistry.kt

Lines changed: 10 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,10 @@ public interface ViewRegistry {
8383

8484
public companion object : ViewEnvironmentKey<ViewRegistry>(ViewRegistry::class) {
8585
override val default: ViewRegistry get() = ViewRegistry()
86+
override fun combine(
87+
left: ViewRegistry,
88+
right: ViewRegistry
89+
): ViewRegistry = left.merge(right)
8690
}
8791
}
8892

@@ -103,24 +107,24 @@ public fun ViewRegistry(vararg bindings: Entry<*>): ViewRegistry =
103107
public fun ViewRegistry(): ViewRegistry = TypedViewRegistry()
104108

105109
/**
106-
* @throws IllegalArgumentException if the receiver already has a matching [entry].
110+
* Transforms the receiver to add [entry], throwing [IllegalArgumentException] if the receiver
111+
* already has a matching [entry]. Use [merge] to replace an existing entry with a new one.
107112
*/
108113
@WorkflowUiExperimentalApi
109114
public operator fun ViewRegistry.plus(entry: Entry<*>): ViewRegistry =
110115
this + ViewRegistry(entry)
111116

112-
/** @throws IllegalArgumentException if other has redundant entries. */
117+
/**
118+
* Transforms the receiver to add all entries from [other], throwing [IllegalArgumentException]
119+
* if the receiver already has any matching [entry]. Use [merge] to replace existing entries.
120+
*/
113121
@WorkflowUiExperimentalApi
114122
public operator fun ViewRegistry.plus(other: ViewRegistry): ViewRegistry {
115123
if (other.keys.isEmpty()) return this
116124
if (this.keys.isEmpty()) return other
117125
return CompositeViewRegistry(this, other)
118126
}
119127

120-
/**
121-
* Replaces the existing [ViewRegistry] of the receiver with [registry]. Use
122-
* [ViewEnvironment.merge] to combine them instead.
123-
*/
124128
@WorkflowUiExperimentalApi
125129
public operator fun ViewEnvironment.plus(registry: ViewRegistry): ViewEnvironment {
126130
if (this[ViewRegistry] === registry) return this
@@ -144,32 +148,3 @@ public infix fun ViewRegistry.merge(other: ViewRegistry): ViewRegistry {
144148
.toTypedArray()
145149
.let { ViewRegistry(*it) }
146150
}
147-
148-
/**
149-
* Merges the [ViewRegistry] of the receiver with [registry]. If there are conflicting entries,
150-
* those in [registry] are preferred.
151-
*/
152-
@WorkflowUiExperimentalApi
153-
public infix fun ViewEnvironment.merge(registry: ViewRegistry): ViewEnvironment {
154-
if (this[ViewRegistry] === registry) return this
155-
if (registry.keys.isEmpty()) return this
156-
157-
val merged = this[ViewRegistry] merge registry
158-
return this + merged
159-
}
160-
161-
/**
162-
* Combines the receiving [ViewEnvironment] with [other], taking care to merge
163-
* their [ViewRegistry] entries. Any other conflicting values in [other] replace those
164-
* in the receiver.
165-
*/
166-
@WorkflowUiExperimentalApi
167-
public infix fun ViewEnvironment.merge(other: ViewEnvironment): ViewEnvironment {
168-
if (this == other) return this
169-
if (other.map.isEmpty()) return this
170-
if (this.map.isEmpty()) return other
171-
172-
val oldReg = this[ViewRegistry]
173-
val newReg = other[ViewRegistry]
174-
return this + other + (ViewRegistry to oldReg.merge(newReg))
175-
}

0 commit comments

Comments
 (0)