Skip to content

Commit 85b3449

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 85b3449

File tree

13 files changed

+100
-111
lines changed

13 files changed

+100
-111
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: 31 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -19,19 +19,30 @@ 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)
27+
?.let { oldValue -> newKey to newKey.combine(oldValue, newValue) }
28+
?: pair
29+
@Suppress("DEPRECATION")
30+
return ViewEnvironment(map + newPair)
31+
}
2832

2933
@Suppress("DEPRECATION")
3034
public operator fun plus(other: ViewEnvironment): ViewEnvironment {
3135
if (this == other) return this
3236
if (other.map.isEmpty()) return this
33-
if (this.map.isEmpty()) return other
34-
return ViewEnvironment(map + other.map)
37+
if (map.isEmpty()) return other
38+
val newMap = map.toMutableMap()
39+
other.map.entries.forEach { (key, value) ->
40+
@Suppress("UNCHECKED_CAST")
41+
newMap[key] = getOrNull(key as ViewEnvironmentKey<Any>)
42+
?.let { oldValue -> key.combine(oldValue, value) }
43+
?: value
44+
}
45+
return ViewEnvironment(newMap)
3546
}
3647

3748
override fun toString(): String = "ViewEnvironment($map)"
@@ -41,6 +52,9 @@ constructor(
4152

4253
override fun hashCode(): Int = map.hashCode()
4354

55+
@Suppress("UNCHECKED_CAST")
56+
private fun <T : Any> getOrNull(key: ViewEnvironmentKey<T>): T? = map[key] as? T
57+
4458
public companion object {
4559
@Suppress("DEPRECATION")
4660
public val EMPTY: ViewEnvironment = ViewEnvironment()
@@ -57,6 +71,15 @@ public abstract class ViewEnvironmentKey<T : Any>(
5771
) {
5872
public abstract val default: T
5973

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

6689
final override fun hashCode(): Int = type.hashCode()
6790

68-
override fun toString(): String {
91+
final override fun toString(): String {
6992
return "${this::class.simpleName}(${type.simpleName})"
7093
}
7194
}
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)