Skip to content

Commit 3f2f344

Browse files
committed
Decouples ViewRegistry from ViewFactory
1 parent e0e59c8 commit 3f2f344

File tree

12 files changed

+71
-54
lines changed

12 files changed

+71
-54
lines changed

workflow-ui/compose-tooling/src/main/java/com/squareup/workflow1/ui/compose/tooling/PreviewViewEnvironment.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ private class PreviewViewRegistry<RenderingT : Any>(
4848
override val keys: Set<KClass<*>> get() = mainFactory?.let { setOf(it.type) } ?: emptySet()
4949

5050
@Suppress("UNCHECKED_CAST")
51-
override fun <RenderingT : Any> getFactoryFor(
51+
override fun <RenderingT : Any> getEntryFor(
5252
renderingType: KClass<out RenderingT>
5353
): ViewFactory<RenderingT> = when (renderingType) {
5454
mainFactory?.type -> mainFactory

workflow-ui/compose/src/main/java/com/squareup/workflow1/ui/compose/CompositionRoot.kt

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -84,23 +84,24 @@ public fun ViewRegistry.withCompositionRoot(root: CompositionRoot): ViewRegistry
8484

8585
/**
8686
* Applies [transform] to each [ViewFactory] in this registry. Transformations are applied lazily,
87-
* at the time of lookup via [ViewRegistry.getFactoryFor].
87+
* at the time of lookup via [ViewRegistry.getEntryFor].
8888
*/
8989
@WorkflowUiExperimentalApi
9090
private fun ViewRegistry.mapFactories(
9191
transform: (ViewFactory<*>) -> ViewFactory<*>
9292
): ViewRegistry = object : ViewRegistry {
9393
override val keys: Set<KClass<*>> get() = this@mapFactories.keys
9494

95-
override fun <RenderingT : Any> getFactoryFor(
95+
override fun <RenderingT : Any> getEntryFor(
9696
renderingType: KClass<out RenderingT>
9797
): ViewFactory<RenderingT> {
9898
val factoryFor =
99-
this@mapFactories.getFactoryFor(renderingType) ?: throw IllegalArgumentException(
100-
"A ${ViewFactory::class.qualifiedName} should have been registered to display " +
101-
"${renderingType.qualifiedName} instances, or that class should implement " +
102-
"${AndroidViewRendering::class.simpleName}<${renderingType.simpleName}>."
103-
)
99+
(this@mapFactories.getEntryFor(renderingType) as? ViewFactory<*>)
100+
?: throw IllegalArgumentException(
101+
"A ${ViewFactory::class.qualifiedName} should have been registered to display " +
102+
"${renderingType.qualifiedName} instances, or that class should implement " +
103+
"${AndroidViewRendering::class.simpleName}<${renderingType.simpleName}>."
104+
)
104105
val transformedFactory = transform(factoryFor)
105106
check(transformedFactory.type == renderingType) {
106107
"Expected transform to return a ViewFactory that is compatible with $renderingType, " +

workflow-ui/compose/src/main/java/com/squareup/workflow1/ui/compose/WorkflowRendering.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,9 @@ import com.squareup.workflow1.ui.Compatible
2121
import com.squareup.workflow1.ui.ViewEnvironment
2222
import com.squareup.workflow1.ui.ViewFactory
2323
import com.squareup.workflow1.ui.ViewRegistry
24-
import com.squareup.workflow1.ui.androidx.WorkflowLifecycleOwner
2524
import com.squareup.workflow1.ui.WorkflowUiExperimentalApi
2625
import com.squareup.workflow1.ui.WorkflowViewStub
26+
import com.squareup.workflow1.ui.androidx.WorkflowLifecycleOwner
2727
import com.squareup.workflow1.ui.getFactoryForRendering
2828
import com.squareup.workflow1.ui.getShowRendering
2929
import com.squareup.workflow1.ui.showRendering
@@ -82,7 +82,7 @@ import kotlin.reflect.KClass
8282
// intentionally don't ask it for a new instance every time to match the behavior of
8383
// WorkflowViewStub and other containers, which only ask for a new factory when the rendering is
8484
// incompatible.
85-
viewEnvironment[ViewRegistry]
85+
viewEnvironment
8686
// Can't use ViewRegistry.buildView here since we need the factory to convert it to a
8787
// compose one.
8888
.getFactoryForRendering(rendering)

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

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,13 @@ public final class com/squareup/workflow1/ui/AndroidRenderWorkflowKt {
77
public static synthetic fun renderWorkflowIn$default (Lcom/squareup/workflow1/Workflow;Lkotlinx/coroutines/CoroutineScope;Lkotlinx/coroutines/flow/StateFlow;Landroidx/lifecycle/SavedStateHandle;Ljava/util/List;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lkotlinx/coroutines/flow/StateFlow;
88
}
99

10+
public final class com/squareup/workflow1/ui/AndroidViewEnvironmentKt {
11+
public static final fun buildView (Lcom/squareup/workflow1/ui/ViewEnvironment;Ljava/lang/Object;Landroid/content/Context;Landroid/view/ViewGroup;Lkotlin/jvm/functions/Function1;)Landroid/view/View;
12+
public static synthetic fun buildView$default (Lcom/squareup/workflow1/ui/ViewEnvironment;Ljava/lang/Object;Landroid/content/Context;Landroid/view/ViewGroup;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Landroid/view/View;
13+
public static final fun getFactoryForRendering (Lcom/squareup/workflow1/ui/ViewEnvironment;Ljava/lang/Object;)Lcom/squareup/workflow1/ui/ViewFactory;
14+
public static final fun showFirstRendering (Landroid/view/View;)V
15+
}
16+
1017
public abstract interface class com/squareup/workflow1/ui/AndroidViewRendering {
1118
public abstract fun getViewFactory ()Lcom/squareup/workflow1/ui/ViewFactory;
1219
}
@@ -80,9 +87,8 @@ public final class com/squareup/workflow1/ui/ViewBindingViewFactory : com/square
8087
public fun getType ()Lkotlin/reflect/KClass;
8188
}
8289

83-
public abstract interface class com/squareup/workflow1/ui/ViewFactory {
90+
public abstract interface class com/squareup/workflow1/ui/ViewFactory : com/squareup/workflow1/ui/ViewRegistry$Entry {
8491
public abstract fun buildView (Ljava/lang/Object;Lcom/squareup/workflow1/ui/ViewEnvironment;Landroid/content/Context;Landroid/view/ViewGroup;)Landroid/view/View;
85-
public abstract fun getType ()Lkotlin/reflect/KClass;
8692
}
8793

8894
public final class com/squareup/workflow1/ui/ViewFactory$DefaultImpls {
@@ -91,7 +97,7 @@ public final class com/squareup/workflow1/ui/ViewFactory$DefaultImpls {
9197

9298
public abstract interface class com/squareup/workflow1/ui/ViewRegistry {
9399
public static final field Companion Lcom/squareup/workflow1/ui/ViewRegistry$Companion;
94-
public abstract fun getFactoryFor (Lkotlin/reflect/KClass;)Lcom/squareup/workflow1/ui/ViewFactory;
100+
public abstract fun getEntryFor (Lkotlin/reflect/KClass;)Lcom/squareup/workflow1/ui/ViewRegistry$Entry;
95101
public abstract fun getKeys ()Ljava/util/Set;
96102
}
97103

@@ -100,15 +106,15 @@ public final class com/squareup/workflow1/ui/ViewRegistry$Companion : com/square
100106
public synthetic fun getDefault ()Ljava/lang/Object;
101107
}
102108

109+
public abstract interface class com/squareup/workflow1/ui/ViewRegistry$Entry {
110+
public abstract fun getType ()Lkotlin/reflect/KClass;
111+
}
112+
103113
public final class com/squareup/workflow1/ui/ViewRegistryKt {
104114
public static final fun ViewRegistry ()Lcom/squareup/workflow1/ui/ViewRegistry;
105-
public static final fun ViewRegistry ([Lcom/squareup/workflow1/ui/ViewFactory;)Lcom/squareup/workflow1/ui/ViewRegistry;
106-
public static final fun buildView (Lcom/squareup/workflow1/ui/ViewRegistry;Ljava/lang/Object;Lcom/squareup/workflow1/ui/ViewEnvironment;Landroid/content/Context;Landroid/view/ViewGroup;Lkotlin/jvm/functions/Function1;)Landroid/view/View;
107-
public static synthetic fun buildView$default (Lcom/squareup/workflow1/ui/ViewRegistry;Ljava/lang/Object;Lcom/squareup/workflow1/ui/ViewEnvironment;Landroid/content/Context;Landroid/view/ViewGroup;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Landroid/view/View;
108-
public static final fun getFactoryForRendering (Lcom/squareup/workflow1/ui/ViewRegistry;Ljava/lang/Object;)Lcom/squareup/workflow1/ui/ViewFactory;
109-
public static final fun plus (Lcom/squareup/workflow1/ui/ViewRegistry;Lcom/squareup/workflow1/ui/ViewFactory;)Lcom/squareup/workflow1/ui/ViewRegistry;
115+
public static final fun ViewRegistry ([Lcom/squareup/workflow1/ui/ViewRegistry$Entry;)Lcom/squareup/workflow1/ui/ViewRegistry;
116+
public static final fun plus (Lcom/squareup/workflow1/ui/ViewRegistry;Lcom/squareup/workflow1/ui/ViewRegistry$Entry;)Lcom/squareup/workflow1/ui/ViewRegistry;
110117
public static final fun plus (Lcom/squareup/workflow1/ui/ViewRegistry;Lcom/squareup/workflow1/ui/ViewRegistry;)Lcom/squareup/workflow1/ui/ViewRegistry;
111-
public static final fun showFirstRendering (Landroid/view/View;)V
112118
}
113119

114120
public final class com/squareup/workflow1/ui/ViewShowRenderingKt {

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

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
package com.squareup.workflow1.ui
22

3-
import android.view.View
43
import android.content.Context
4+
import android.view.View
55
import android.view.ViewGroup
66

77
/**
@@ -11,7 +11,7 @@ import android.view.ViewGroup
1111
* Returns the [ViewFactory] that builds [View] instances suitable to display the given [rendering],
1212
* via subsequent calls to [View.showRendering].
1313
*
14-
* Prefers factories found via [ViewRegistry.getFactoryFor]. If that returns null, falls
14+
* Prefers factories found via [ViewRegistry.getEntryFor]. If that returns null, falls
1515
* back to the factory provided by the rendering's implementation of
1616
* [AndroidViewRendering.viewFactory], if there is one. Note that this means that a
1717
* compile time [AndroidViewRendering.viewFactory] binding can be overridden at runtime.
@@ -33,7 +33,7 @@ import android.view.ViewGroup
3333
public fun <RenderingT : Any>
3434
ViewEnvironment.getFactoryForRendering(rendering: RenderingT): ViewFactory<RenderingT> {
3535
@Suppress("UNCHECKED_CAST")
36-
return get(ViewRegistry).getFactoryFor(rendering::class)
36+
return (get(ViewRegistry).getEntryFor(rendering::class) as? ViewFactory<RenderingT>)
3737
?: (rendering as? AndroidViewRendering<*>)?.viewFactory as? ViewFactory<RenderingT>
3838
?: (rendering as? Named<*>)?.let { NamedViewFactory as ViewFactory<RenderingT> }
3939
?: throw IllegalArgumentException(
@@ -79,7 +79,11 @@ public fun <RenderingT : Any> ViewEnvironment.buildView(
7979
container: ViewGroup? = null,
8080
initializeView: View.() -> Unit = { showFirstRendering() }
8181
): View {
82-
return getFactoryForRendering(initialRendering).buildView(
82+
val entry = getFactoryForRendering(initialRendering)
83+
val viewFactory = (entry as? ViewFactory<RenderingT>)
84+
?: error("Require a ViewFactory for $initialRendering, found $entry")
85+
86+
return viewFactory.buildView(
8387
initialRendering, this, contextForNewView, container
8488
).also { view ->
8589
checkNotNull(view.showRenderingTag) {

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

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,18 @@
11
package com.squareup.workflow1.ui
22

3+
import com.squareup.workflow1.ui.ViewRegistry.Entry
34
import kotlin.reflect.KClass
45

56
/**
6-
* A [ViewRegistry] that contains only other registries and delegates to their [getFactoryFor]
7+
* A [ViewRegistry] that contains only other registries and delegates to their [getEntryFor]
78
* methods.
89
*
910
* Whenever any registries are combined using the [ViewRegistry] factory functions or `plus`
1011
* operators, an instance of this class is returned. All registries' keys are checked at
1112
* construction to ensure that no duplicate keys exist.
1213
*
13-
* The implementation of [getFactoryFor] consists of a single layer of indirection – the responsible
14-
* [ViewRegistry] is looked up in a map by key, and then that registry's [getFactoryFor] is called.
14+
* The implementation of [getEntryFor] consists of a single layer of indirection – the responsible
15+
* [ViewRegistry] is looked up in a map by key, and then that registry's [getEntryFor] is called.
1516
*
1617
* When multiple [CompositeViewRegistry]s are combined, they are flattened, so that there is never
1718
* more than one layer of indirection. In other words, a [CompositeViewRegistry] will never contain
@@ -26,9 +27,9 @@ internal class CompositeViewRegistry private constructor(
2627

2728
override val keys: Set<KClass<*>> get() = registriesByKey.keys
2829

29-
override fun <RenderingT : Any> getFactoryFor(
30+
override fun <RenderingT : Any> getEntryFor(
3031
renderingType: KClass<out RenderingT>
31-
): ViewFactory<RenderingT>? = registriesByKey[renderingType]?.getFactoryFor(renderingType)
32+
): Entry<RenderingT>? = registriesByKey[renderingType]?.getEntryFor(renderingType)
3233

3334
companion object {
3435
private fun mergeRegistries(vararg registries: ViewRegistry): Map<KClass<*>, ViewRegistry> {
Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package com.squareup.workflow1.ui
22

3+
import com.squareup.workflow1.ui.ViewRegistry.Entry
34
import kotlin.reflect.KClass
45

56
/**
@@ -8,25 +9,25 @@ import kotlin.reflect.KClass
89
*/
910
@WorkflowUiExperimentalApi
1011
internal class TypedViewRegistry private constructor(
11-
private val bindings: Map<KClass<*>, ViewFactory<*>>
12+
private val bindings: Map<KClass<*>, Entry<*>>
1213
) : ViewRegistry {
1314

14-
constructor(vararg bindings: ViewFactory<*>) : this(
15+
constructor(vararg bindings: Entry<*>) : this(
1516
bindings.map { it.type to it }
1617
.toMap()
1718
.apply {
1819
check(keys.size == bindings.size) {
1920
"${bindings.map { it.type }} must not have duplicate entries."
2021
}
21-
} as Map<KClass<*>, ViewFactory<*>>
22+
} as Map<KClass<*>, Entry<*>>
2223
)
2324

2425
override val keys: Set<KClass<*>> get() = bindings.keys
2526

26-
override fun <RenderingT : Any> getFactoryFor(
27+
override fun <RenderingT : Any> getEntryFor(
2728
renderingType: KClass<out RenderingT>
28-
): ViewFactory<RenderingT>? {
29+
): Entry<RenderingT>? {
2930
@Suppress("UNCHECKED_CAST")
30-
return bindings[renderingType] as? ViewFactory<RenderingT>
31+
return bindings[renderingType] as? Entry<RenderingT>
3132
}
3233
}

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

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ package com.squareup.workflow1.ui
33
import android.content.Context
44
import android.view.View
55
import android.view.ViewGroup
6-
import kotlin.reflect.KClass
76

87
/**
98
* Factory for [View] instances that can show renderings of type[RenderingT].
@@ -20,9 +19,7 @@ import kotlin.reflect.KClass
2019
* avoid coupling your workflow directly to the Android runtime, see [ViewRegistry].
2120
*/
2221
@WorkflowUiExperimentalApi
23-
public interface ViewFactory<in RenderingT : Any> {
24-
public val type: KClass<in RenderingT>
25-
22+
public interface ViewFactory<in RenderingT : Any> : ViewRegistry.Entry<RenderingT> {
2623
/**
2724
* Returns a View ready to display [initialRendering] (and any succeeding values)
2825
* via [View.showRendering].

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

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
package com.squareup.workflow1.ui
44

5+
import com.squareup.workflow1.ui.ViewRegistry.Entry
56
import kotlin.reflect.KClass
67

78
/**
@@ -62,9 +63,13 @@ import kotlin.reflect.KClass
6263
*/
6364
@WorkflowUiExperimentalApi
6465
public interface ViewRegistry {
66+
public interface Entry<in RenderingT : Any> {
67+
public val type: KClass<in RenderingT>
68+
}
69+
6570
/**
6671
* The set of unique keys which this registry can derive from the renderings passed to
67-
* [getFactoryFor] and for which it knows how to create views.
72+
* [getEntryFor] and for which it knows how to create views.
6873
*
6974
* Used to ensure that duplicate bindings are never registered.
7075
*/
@@ -76,17 +81,17 @@ public interface ViewRegistry {
7681
* Returns the [ViewFactory] that was registered for the given [renderingType], or null
7782
* if none was found.
7883
*/
79-
public fun <RenderingT : Any> getFactoryFor(
84+
public fun <RenderingT : Any> getEntryFor(
8085
renderingType: KClass<out RenderingT>
81-
): ViewFactory<RenderingT>?
86+
): Entry<RenderingT>?
8287

8388
public companion object : ViewEnvironmentKey<ViewRegistry>(ViewRegistry::class) {
8489
override val default: ViewRegistry get() = ViewRegistry()
8590
}
8691
}
8792

8893
@WorkflowUiExperimentalApi
89-
public fun ViewRegistry(vararg bindings: ViewFactory<*>): ViewRegistry =
94+
public fun ViewRegistry(vararg bindings: Entry<*>): ViewRegistry =
9095
TypedViewRegistry(*bindings)
9196

9297
/**
@@ -98,7 +103,7 @@ public fun ViewRegistry(vararg bindings: ViewFactory<*>): ViewRegistry =
98103
public fun ViewRegistry(): ViewRegistry = TypedViewRegistry()
99104

100105
@WorkflowUiExperimentalApi
101-
public operator fun ViewRegistry.plus(binding: ViewFactory<*>): ViewRegistry =
106+
public operator fun ViewRegistry.plus(binding: Entry<*>): ViewRegistry =
102107
this + ViewRegistry(binding)
103108

104109
@WorkflowUiExperimentalApi

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

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
package com.squareup.workflow1.ui
22

33
import com.google.common.truth.Truth.assertThat
4+
import com.squareup.workflow1.ui.ViewRegistry.Entry
45
import org.junit.Test
56
import kotlin.reflect.KClass
67
import kotlin.test.assertFailsWith
78

89
@OptIn(WorkflowUiExperimentalApi::class)
9-
class CompositeViewRegistryTest {
10+
internal class CompositeViewRegistryTest {
1011

1112
@Test fun `constructor throws on duplicates`() {
1213
val fooBarRegistry = TestRegistry(setOf(FooRendering::class, BarRendering::class))
@@ -34,19 +35,19 @@ class CompositeViewRegistryTest {
3435
val bazRegistry = TestRegistry(factories = mapOf(BazRendering::class to bazFactory))
3536
val registry = CompositeViewRegistry(fooBarRegistry, bazRegistry)
3637

37-
assertThat(registry.getFactoryFor(FooRendering::class))
38+
assertThat(registry.getEntryFor(FooRendering::class))
3839
.isSameInstanceAs(fooFactory)
39-
assertThat(registry.getFactoryFor(BarRendering::class))
40+
assertThat(registry.getEntryFor(BarRendering::class))
4041
.isSameInstanceAs(barFactory)
41-
assertThat(registry.getFactoryFor(BazRendering::class))
42+
assertThat(registry.getEntryFor(BazRendering::class))
4243
.isSameInstanceAs(bazFactory)
4344
}
4445

4546
@Test fun `getFactoryFor returns null on missing registry`() {
4647
val fooRegistry = TestRegistry(setOf(FooRendering::class))
4748
val registry = CompositeViewRegistry(fooRegistry)
4849

49-
assertThat(registry.getFactoryFor(BarRendering::class)).isNull()
50+
assertThat(registry.getEntryFor(BarRendering::class)).isNull()
5051
}
5152

5253
@Test fun `keys includes all composite registries' keys`() {
@@ -71,8 +72,8 @@ class CompositeViewRegistryTest {
7172
override val keys: Set<KClass<*>> get() = factories.keys
7273

7374
@Suppress("UNCHECKED_CAST")
74-
override fun <RenderingT : Any> getFactoryFor(
75+
override fun <RenderingT : Any> getEntryFor(
7576
renderingType: KClass<out RenderingT>
76-
): ViewFactory<RenderingT> = factories.getValue(renderingType) as ViewFactory<RenderingT>
77+
): Entry<RenderingT> = factories.getValue(renderingType) as Entry<RenderingT>
7778
}
7879
}

0 commit comments

Comments
 (0)