Skip to content

Commit d923eac

Browse files
committed
Full backward compatibility for Screen.
Extends the legacy `ViewRegistry.getFactoryForRendering` to handle any arbitrary `Screen` implementation, making every bit of code that works directly with `ViewRegistry.buildView` (including `DecorativeViewFactory`) future proof. Allows us to delete the legacy factory that had been created specifically for `BackStackScreen`.
1 parent 2844e65 commit d923eac

File tree

8 files changed

+101
-51
lines changed

8 files changed

+101
-51
lines changed

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

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -48,13 +48,6 @@ public final class com/squareup/workflow1/ui/BackPressHandlerKt {
4848
public static final fun setBackPressedHandler (Landroid/view/View;Lkotlin/jvm/functions/Function0;)V
4949
}
5050

51-
public final class com/squareup/workflow1/ui/BackStackScreenLegacyViewFactory : com/squareup/workflow1/ui/ViewFactory {
52-
public static final field INSTANCE Lcom/squareup/workflow1/ui/BackStackScreenLegacyViewFactory;
53-
public fun buildView (Lcom/squareup/workflow1/ui/container/BackStackScreen;Lcom/squareup/workflow1/ui/ViewEnvironment;Landroid/content/Context;Landroid/view/ViewGroup;)Landroid/view/View;
54-
public synthetic fun buildView (Ljava/lang/Object;Lcom/squareup/workflow1/ui/ViewEnvironment;Landroid/content/Context;Landroid/view/ViewGroup;)Landroid/view/View;
55-
public fun getType ()Lkotlin/reflect/KClass;
56-
}
57-
5851
public final class com/squareup/workflow1/ui/BuilderViewFactory : com/squareup/workflow1/ui/ViewFactory {
5952
public fun <init> (Lkotlin/reflect/KClass;Lkotlin/jvm/functions/Function4;)V
6053
public fun buildView (Ljava/lang/Object;Lcom/squareup/workflow1/ui/ViewEnvironment;Landroid/content/Context;Landroid/view/ViewGroup;)Landroid/view/View;
@@ -99,6 +92,13 @@ public final class com/squareup/workflow1/ui/LayoutScreenViewFactory : com/squar
9992
public fun getType ()Lkotlin/reflect/KClass;
10093
}
10194

95+
public final class com/squareup/workflow1/ui/LegacyFactoryForScreenType : com/squareup/workflow1/ui/ViewFactory {
96+
public fun <init> ()V
97+
public fun buildView (Lcom/squareup/workflow1/ui/Screen;Lcom/squareup/workflow1/ui/ViewEnvironment;Landroid/content/Context;Landroid/view/ViewGroup;)Landroid/view/View;
98+
public synthetic fun buildView (Ljava/lang/Object;Lcom/squareup/workflow1/ui/ViewEnvironment;Landroid/content/Context;Landroid/view/ViewGroup;)Landroid/view/View;
99+
public fun getType ()Lkotlin/reflect/KClass;
100+
}
101+
102102
public final class com/squareup/workflow1/ui/NamedViewFactory : com/squareup/workflow1/ui/ViewFactory {
103103
public static final field INSTANCE Lcom/squareup/workflow1/ui/NamedViewFactory;
104104
public fun buildView (Lcom/squareup/workflow1/ui/Named;Lcom/squareup/workflow1/ui/ViewEnvironment;Landroid/content/Context;Landroid/view/ViewGroup;)Landroid/view/View;

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

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ package com.squareup.workflow1.ui
55
import android.content.Context
66
import android.view.View
77
import android.view.ViewGroup
8-
import com.squareup.workflow1.ui.container.BackStackScreen
98
import com.squareup.workflow1.ui.container.EnvironmentScreen
109
import com.squareup.workflow1.ui.container.EnvironmentScreenLegacyViewFactory
1110
import kotlin.reflect.KClass
@@ -36,14 +35,13 @@ public fun <RenderingT : Any> ViewRegistry.getFactoryForRendering(
3635
@Suppress("UNCHECKED_CAST")
3736
return getFactoryFor(rendering::class)
3837
?: (rendering as? AndroidViewRendering<*>)?.viewFactory as? ViewFactory<RenderingT>
38+
?: (rendering as? Named<*>)?.let { NamedViewFactory as ViewFactory<RenderingT> }
3939
?: (rendering as? AsScreen<*>)?.let { AsScreenLegacyViewFactory as ViewFactory<RenderingT> }
40-
?: (rendering as? BackStackScreen<*>)?.let {
41-
BackStackScreenLegacyViewFactory as ViewFactory<RenderingT>
42-
}
4340
?: (rendering as? EnvironmentScreen<*>)?.let {
41+
// Special handling to ensure the custom environment is in play before the view is built.
4442
EnvironmentScreenLegacyViewFactory as ViewFactory<RenderingT>
4543
}
46-
?: (rendering as? Named<*>)?.let { NamedViewFactory as ViewFactory<RenderingT> }
44+
?: (rendering as? Screen)?.let { LegacyFactoryForScreenType() as ViewFactory<RenderingT> }
4745
?: throw IllegalArgumentException(
4846
"A ViewFactory should have been registered to display $rendering, " +
4947
"or that class should implement AndroidViewRendering."

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

Lines changed: 0 additions & 21 deletions
This file was deleted.
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package com.squareup.workflow1.ui
2+
3+
import android.content.Context
4+
import android.view.View
5+
import android.view.ViewGroup
6+
7+
@WorkflowUiExperimentalApi
8+
internal class LegacyFactoryForScreenType : ViewFactory<Screen> {
9+
override val type = Screen::class
10+
11+
override fun buildView(
12+
initialRendering: Screen,
13+
initialViewEnvironment: ViewEnvironment,
14+
contextForNewView: Context,
15+
container: ViewGroup?
16+
): View {
17+
val modernFactory = initialRendering.toViewFactory(initialViewEnvironment)
18+
val holder = modernFactory.buildView(
19+
initialRendering, initialViewEnvironment, contextForNewView, container
20+
)
21+
holder.view.bindShowRendering(initialRendering, initialViewEnvironment) { r, e ->
22+
holder.runner.showRendering(r, e)
23+
}
24+
return holder.view
25+
}
26+
}

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

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,11 @@ public interface ScreenViewHolder<in ScreenT : Screen> {
4242

4343
/**
4444
* Provides access to the [Screen] instance most recently shown in a [ScreenViewHolder]'s
45-
* [view] via [show]. Call [showing] for more convenient access.
45+
* [view] via [show], or through the legacy [View.showRendering] function.
46+
* Call [showing] for more convenient access.
47+
*
48+
* If the host [View] is driven by a non-[Screen] rendering via the legacy
49+
* [View.showRendering] function, it will be returned in an [AsScreen] wrapper.
4650
*/
4751
public object Showing : ViewEnvironmentKey<Screen>(Screen::class) {
4852
override val default: Screen = ShowingNothing

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

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -114,25 +114,35 @@ public fun <RenderingT : Any> View.showRendering(
114114
}
115115

116116
// Update the tag's rendering and viewEnvironment before calling
117-
// the actual showRendering function.
118-
workflowViewState = Started(rendering, viewEnvironment, viewState.showRendering)
117+
// the actual showRendering function. Note that we update both the
118+
// new Showing key and the old workflowViewState backing View.getRendering().
119+
val updatedEnv = viewEnvironment + (Showing to asScreen(rendering))
120+
workflowViewState = Started(rendering, updatedEnv, viewState.showRendering)
121+
119122
viewState.showRendering.invoke(rendering, viewEnvironment)
120123
}
121124
}
122125

123126
/**
124-
* **This will be deprecated in favor of [screenOrNull] very soon.**
125-
*
126-
* Returns the most recent rendering shown by this view cast to [RenderingT],
127+
* Returns the most recent rendering [shown][showRendering] by this [View], cast to [RenderingT];
127128
* or null if [bindShowRendering] has never been called.
128129
*
130+
* Note that this is tied strictly to calls to [showRendering], and does not reflect
131+
* calls to [ScreenViewHolder.show], which is poised to replace that function. For
132+
* reliable access to the latest rendering displayed by a View, use the [Showing]
133+
* [ViewEnvironmentKey].
134+
*
129135
* @throws ClassCastException if the current rendering is not of type [RenderingT]
130136
*/
131137
@WorkflowUiExperimentalApi
132-
// @Deprecated(
133-
// "Replaced by View.screenOrNull",
134-
// ReplaceWith("screenOrNull", "com.squareup.workflow1.ui.screenOrNull")
135-
// )
138+
@Deprecated(
139+
"Replaced by Showing in ViewEnvironment",
140+
ReplaceWith(
141+
"environmentOrNull?.get(Showing)",
142+
"com.squareup.workflow1.ui.environmentOrNull",
143+
"com.squareup.workflow1.ui.ScreenViewHolder.Companion.Showing"
144+
)
145+
)
136146
public inline fun <reified RenderingT : Any> View.getRendering(): RenderingT? {
137147
// Can't use a val because of the parameter type.
138148
return when (val showing = workflowViewStateOrNull?.showing) {

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -153,7 +153,7 @@ public class WorkflowSavedStateRegistryAggregator {
153153
} catch (e: IllegalArgumentException) {
154154
throw IllegalArgumentException(
155155
"Error registering SavedStateProvider: key \"$key\" is already in " +
156-
"use on parent SavedStateRegistryOwner $parentOwner.\n" +
156+
"use on parent SavedStateRegistryOwner $parentOwner. " +
157157
"This is most easily remedied by giving your container Screen rendering a unique " +
158158
"Compatible.compatibilityKey, perhaps by wrapping it with Named.",
159159
e

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

Lines changed: 40 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,10 @@ import android.content.Context
66
import android.view.View
77
import android.view.ViewGroup
88
import com.google.common.truth.Truth.assertThat
9+
import com.squareup.workflow1.ui.ScreenViewHolder.Companion.Showing
910
import com.squareup.workflow1.ui.ViewRegistry.Entry
11+
import com.squareup.workflow1.ui.container.mockView
1012
import org.junit.Test
11-
import org.mockito.kotlin.doReturn
12-
import org.mockito.kotlin.eq
1313
import org.mockito.kotlin.mock
1414
import kotlin.reflect.KClass
1515
import kotlin.test.assertFailsWith
@@ -126,6 +126,23 @@ internal class LegacyAndroidViewRegistryTest {
126126
assertThat(overrideViewRenderingFactory.called).isTrue()
127127
}
128128

129+
@Test fun `buildView auto converts unwrapped Screen and updates Showing correctly`() {
130+
val registry = ViewRegistry()
131+
val view = registry.buildView(ScreenRendering)
132+
view.start()
133+
assertThat(view.getRendering<Any>()).isSameInstanceAs(ScreenRendering)
134+
assertThat(view.environmentOrNull!![Showing]).isSameInstanceAs(ScreenRendering)
135+
}
136+
137+
@Test fun `buildView auto converts wrapped Screen and updates Showing correctly`() {
138+
val registry = ViewRegistry()
139+
val rendering = Named(ScreenRendering, "fnord")
140+
val view = registry.buildView(rendering)
141+
view.start()
142+
assertThat(compatible(view.getRendering()!!, rendering)).isTrue()
143+
assertThat(compatible(view.environmentOrNull!![Showing], asScreen(rendering))).isTrue()
144+
}
145+
129146
@Test fun `ViewRegistry with no arguments infers type`() {
130147
val registry = ViewRegistry()
131148
assertTrue(registry.keys.isEmpty())
@@ -136,7 +153,11 @@ internal class LegacyAndroidViewRegistryTest {
136153
private object BazRendering
137154

138155
private object ViewRendering : AndroidViewRendering<ViewRendering> {
139-
override val viewFactory: TestViewFactory<ViewRendering> = TestViewFactory(ViewRendering::class)
156+
override val viewFactory = TestViewFactory(ViewRendering::class)
157+
}
158+
159+
private object ScreenRendering : AndroidScreen<ScreenRendering> {
160+
override val viewFactory = TestScreenViewFactory(ScreenRendering::class)
140161
}
141162

142163
private val overrideViewRenderingFactory = TestViewFactory(ViewRendering::class)
@@ -167,11 +188,23 @@ internal class LegacyAndroidViewRegistryTest {
167188
container: ViewGroup?
168189
): View {
169190
called = true
170-
return mock {
171-
on {
172-
getTag(eq(com.squareup.workflow1.ui.R.id.legacy_workflow_view_state))
173-
} doReturn (WorkflowViewState.New(initialRendering, initialViewEnvironment, { _, _ -> }))
191+
return mockView().also {
192+
it.bindShowRendering(initialRendering, initialViewEnvironment) { _, _ -> }
174193
}
175194
}
176195
}
196+
197+
@OptIn(WorkflowUiExperimentalApi::class)
198+
private class TestScreenViewFactory<R : Screen>(
199+
override val type: KClass<R>
200+
) : ScreenViewFactory<R> {
201+
override fun buildView(
202+
initialRendering: R,
203+
initialEnvironment: ViewEnvironment,
204+
context: Context,
205+
container: ViewGroup?
206+
): ScreenViewHolder<R> {
207+
return ScreenViewHolder(initialEnvironment, mockView()) { _, _ -> }
208+
}
209+
}
177210
}

0 commit comments

Comments
 (0)