Skip to content

Commit 9c91a20

Browse files
committed
Introduces ScreenViewFactoryFinder
Fixes #594, which was about how impractical it is to wrap the `ViewRegistry` interface, by introducing a higher level interface that's easy to wrap. The problem is that the fundamental method is `getFactoryFor(KClass)`, but lots of crucial behavior (e.g. `AndroidViewRendering` / `AndroidScreenRendering`) is in the `getFactoryForRendering` extension method. But if we change the fundamental method to be instance based instead of type based, we bring back a lot of potential complexity to `ViewRegistry` that the original type-based choice very intentionally restricted. To have our cake and eat it too, we move the extension method to a new `ViewEnvironment` service interface, `ScreenViewFactoryFinder`. Ta da, totally customizable, with the defaults totally built in.
1 parent 6099c59 commit 9c91a20

File tree

5 files changed

+104
-34
lines changed

5 files changed

+104
-34
lines changed

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

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,20 @@ public final class com/squareup/workflow1/ui/ScreenViewFactory$DefaultImpls {
9595
public static synthetic fun buildView$default (Lcom/squareup/workflow1/ui/ScreenViewFactory;Lcom/squareup/workflow1/ui/Screen;Lcom/squareup/workflow1/ui/ViewEnvironment;Landroid/content/Context;Landroid/view/ViewGroup;ILjava/lang/Object;)Landroid/view/View;
9696
}
9797

98+
public abstract interface class com/squareup/workflow1/ui/ScreenViewFactoryFinder {
99+
public static final field Companion Lcom/squareup/workflow1/ui/ScreenViewFactoryFinder$Companion;
100+
public abstract fun getViewFactoryForRendering (Lcom/squareup/workflow1/ui/ViewEnvironment;Lcom/squareup/workflow1/ui/Screen;)Lcom/squareup/workflow1/ui/ScreenViewFactory;
101+
}
102+
103+
public final class com/squareup/workflow1/ui/ScreenViewFactoryFinder$Companion : com/squareup/workflow1/ui/ViewEnvironmentKey {
104+
public fun getDefault ()Lcom/squareup/workflow1/ui/ScreenViewFactoryFinder;
105+
public synthetic fun getDefault ()Ljava/lang/Object;
106+
}
107+
108+
public final class com/squareup/workflow1/ui/ScreenViewFactoryFinder$DefaultImpls {
109+
public static fun getViewFactoryForRendering (Lcom/squareup/workflow1/ui/ScreenViewFactoryFinder;Lcom/squareup/workflow1/ui/ViewEnvironment;Lcom/squareup/workflow1/ui/Screen;)Lcom/squareup/workflow1/ui/ScreenViewFactory;
110+
}
111+
98112
public final class com/squareup/workflow1/ui/ScreenViewFactoryKt {
99113
public static final fun buildView (Lcom/squareup/workflow1/ui/Screen;Lcom/squareup/workflow1/ui/ViewEnvironment;Landroid/content/Context;Landroid/view/ViewGroup;Lcom/squareup/workflow1/ui/ViewStarter;)Landroid/view/View;
100114
public static synthetic fun buildView$default (Lcom/squareup/workflow1/ui/Screen;Lcom/squareup/workflow1/ui/ViewEnvironment;Landroid/content/Context;Landroid/view/ViewGroup;Lcom/squareup/workflow1/ui/ViewStarter;ILjava/lang/Object;)Landroid/view/View;

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import android.view.ViewGroup
88
import com.squareup.workflow1.ui.container.BackStackScreen
99
import kotlin.reflect.KClass
1010

11-
@Deprecated("Use ViewEnvironment.getViewFactoryForRendering()")
11+
@Deprecated("Use ScreenViewFactoryFinder.getViewFactoryForRendering()")
1212
@WorkflowUiExperimentalApi
1313
public fun <RenderingT : Any>
1414
ViewRegistry.getFactoryForRendering(rendering: RenderingT): ViewFactory<RenderingT> {

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

Lines changed: 3 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,6 @@ package com.squareup.workflow1.ui
33
import android.content.Context
44
import android.view.View
55
import android.view.ViewGroup
6-
import com.squareup.workflow1.ui.container.BackStackScreen
7-
import com.squareup.workflow1.ui.container.BackStackScreenViewFactory
8-
import com.squareup.workflow1.ui.container.BodyAndModalsContainer
9-
import com.squareup.workflow1.ui.container.BodyAndModalsScreen
10-
import com.squareup.workflow1.ui.container.EnvironmentScreen
11-
import com.squareup.workflow1.ui.container.EnvironmentScreenViewFactory
126

137
/**
148
* Factory for [View] instances that can show renderings of type [RenderingT] : [Screen].
@@ -63,7 +57,9 @@ public fun <ScreenT : Screen> ScreenT.buildView(
6357
container: ViewGroup? = null,
6458
viewStarter: ViewStarter? = null,
6559
): View {
66-
val viewFactory = viewEnvironment.getViewFactoryForRendering(this)
60+
val viewFactory = viewEnvironment[ScreenViewFactoryFinder].getViewFactoryForRendering(
61+
viewEnvironment, this
62+
)
6763

6864
return viewFactory.buildView(this, viewEnvironment, contextForNewView, container).also { view ->
6965
checkNotNull(view.workflowViewStateOrNull) {
@@ -97,28 +93,3 @@ public fun interface ViewStarter {
9793
doStart: () -> Unit
9894
)
9995
}
100-
101-
@WorkflowUiExperimentalApi
102-
internal fun <ScreenT : Screen>
103-
ViewEnvironment.getViewFactoryForRendering(rendering: ScreenT): ScreenViewFactory<ScreenT> {
104-
val entry = get(ViewRegistry).getEntryFor(rendering::class)
105-
106-
@Suppress("UNCHECKED_CAST", "DEPRECATION")
107-
return (entry as? ScreenViewFactory<ScreenT>)
108-
?: (rendering as? AndroidScreen<*>)?.viewFactory as? ScreenViewFactory<ScreenT>
109-
?: (rendering as? AsScreen<*>)?.let { AsScreenViewFactory as ScreenViewFactory<ScreenT> }
110-
?: (rendering as? BackStackScreen<*>)?.let {
111-
BackStackScreenViewFactory as ScreenViewFactory<ScreenT>
112-
}
113-
?: (rendering as? BodyAndModalsScreen<*, *>)?.let {
114-
BodyAndModalsContainer as ScreenViewFactory<ScreenT>
115-
}
116-
?: (rendering as? NamedScreen<*>)?.let { NamedScreenViewFactory as ScreenViewFactory<ScreenT> }
117-
?: (rendering as? EnvironmentScreen<*>)?.let {
118-
EnvironmentScreenViewFactory as ScreenViewFactory<ScreenT>
119-
}
120-
?: throw IllegalArgumentException(
121-
"A ScreenViewFactory should have been registered to display $rendering, " +
122-
"or that class should implement AndroidScreen. Instead found $entry."
123-
)
124-
}
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
package com.squareup.workflow1.ui
2+
3+
import com.squareup.workflow1.ui.container.BackStackScreen
4+
import com.squareup.workflow1.ui.container.BackStackScreenViewFactory
5+
import com.squareup.workflow1.ui.container.BodyAndModalsContainer
6+
import com.squareup.workflow1.ui.container.BodyAndModalsScreen
7+
import com.squareup.workflow1.ui.container.EnvironmentScreen
8+
import com.squareup.workflow1.ui.container.EnvironmentScreenViewFactory
9+
10+
/**
11+
* [ViewEnvironment] service object used by [Screen.buildView] to find the right
12+
* [ScreenViewFactory]. The default implementation makes [AndroidScreen] work
13+
* and provides default bindings for [NamedScreen], [EnvironmentScreen], [BackStackScreen],
14+
* etc.
15+
*
16+
* Here is how this hook could be used to provide a custom view to handle [BackStackScreen]:
17+
*
18+
* object MyViewFactory : ScreenViewFactory<BackStackScreen<*>>
19+
* by ManualScreenViewFactory(
20+
* type = BackStackScreen::class,
21+
* viewConstructor = { initialRendering, initialEnv, context, _ ->
22+
* MyBackStackContainer(context)
23+
* .apply {
24+
* layoutParams = (LayoutParams(MATCH_PARENT, MATCH_PARENT))
25+
* bindShowRendering(initialRendering, initialEnv, ::update)
26+
* }
27+
* }
28+
* )
29+
*
30+
* object MyFinder : ScreenViewFactoryFinder {
31+
* @Suppress("UNCHECKED_CAST")
32+
* if (rendering is BackStackScreen<*>)
33+
* return MyViewFactory as ScreenViewFactory<ScreenT>
34+
* return super.getViewFactoryForRendering(environment, rendering)
35+
* }
36+
*
37+
* class MyViewModel(savedState: SavedStateHandle) : ViewModel() {
38+
* val renderings: StateFlow<MyRootRendering> by lazy {
39+
* val customized = ViewEnvironment() + (ScreenViewFactoryFinder to MyFinder)
40+
* renderWorkflowIn(
41+
* workflow = MyRootWorkflow.withEnvironment(customized),
42+
* scope = viewModelScope,
43+
* savedStateHandle = savedState
44+
* )
45+
* }
46+
* }
47+
*/
48+
49+
@WorkflowUiExperimentalApi
50+
public interface ScreenViewFactoryFinder {
51+
public fun <ScreenT : Screen> getViewFactoryForRendering(
52+
environment: ViewEnvironment,
53+
rendering: ScreenT
54+
): ScreenViewFactory<ScreenT> {
55+
val entry = environment[ViewRegistry].getEntryFor(rendering::class)
56+
57+
@Suppress("UNCHECKED_CAST", "DEPRECATION")
58+
return (entry as? ScreenViewFactory<ScreenT>)
59+
?: (rendering as? AndroidScreen<*>)?.viewFactory as? ScreenViewFactory<ScreenT>
60+
?: (rendering as? AsScreen<*>)?.let { AsScreenViewFactory as ScreenViewFactory<ScreenT> }
61+
?: (rendering as? BackStackScreen<*>)?.let {
62+
BackStackScreenViewFactory as ScreenViewFactory<ScreenT>
63+
}
64+
?: (rendering as? BodyAndModalsScreen<*, *>)?.let {
65+
BodyAndModalsContainer as ScreenViewFactory<ScreenT>
66+
}
67+
?: (rendering as? NamedScreen<*>)?.let {
68+
NamedScreenViewFactory as ScreenViewFactory<ScreenT>
69+
}
70+
?: (rendering as? EnvironmentScreen<*>)?.let {
71+
EnvironmentScreenViewFactory as ScreenViewFactory<ScreenT>
72+
}
73+
?: throw IllegalArgumentException(
74+
"A ScreenViewFactory should have been registered to display $rendering, " +
75+
"or that class should implement AndroidScreen. Instead found $entry."
76+
)
77+
}
78+
79+
public companion object : ViewEnvironmentKey<ScreenViewFactoryFinder>(
80+
ScreenViewFactoryFinder::class
81+
) {
82+
override val default: ScreenViewFactoryFinder
83+
get() = object : ScreenViewFactoryFinder {}
84+
}
85+
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ internal class ScreenViewFactoryTest {
7474

7575
return mock {
7676
on {
77-
getTag(eq(com.squareup.workflow1.ui.R.id.workflow_ui_view_state))
77+
getTag(eq(R.id.workflow_ui_view_state))
7878
} doReturn (WorkflowViewState.New(initialRendering, initialViewEnvironment, { _, _ -> }))
7979
}
8080
}

0 commit comments

Comments
 (0)