From e50052d01e9c2bb2c457556542f6804b367b20ec Mon Sep 17 00:00:00 2001 From: Ray Ryan Date: Wed, 8 Dec 2021 08:42:57 -0800 Subject: [PATCH 1/2] Adds (failing) DecorativeViewFactory double update test --- .../workflow1/ui/DecorativeViewFactoryTest.kt | 78 +++++++++++++++++++ 1 file changed, 78 insertions(+) diff --git a/workflow-ui/core-android/src/androidTest/java/com/squareup/workflow1/ui/DecorativeViewFactoryTest.kt b/workflow-ui/core-android/src/androidTest/java/com/squareup/workflow1/ui/DecorativeViewFactoryTest.kt index 8220c1b6a..9999e4d57 100644 --- a/workflow-ui/core-android/src/androidTest/java/com/squareup/workflow1/ui/DecorativeViewFactoryTest.kt +++ b/workflow-ui/core-android/src/androidTest/java/com/squareup/workflow1/ui/DecorativeViewFactoryTest.kt @@ -101,6 +101,80 @@ internal class DecorativeViewFactoryTest { ) } + // https://github.com/square/workflow-kotlin/issues/597 + @Test fun double_wrapping_only_calls_showRendering_once() { + val events = mutableListOf() + + val innerViewFactory = object : ViewFactory { + override val type = InnerRendering::class + override fun buildView( + initialRendering: InnerRendering, + initialViewEnvironment: ViewEnvironment, + contextForNewView: Context, + container: ViewGroup? + ): View = InnerView(contextForNewView).apply { + bindShowRendering(initialRendering, initialViewEnvironment) { rendering, _ -> + events += "inner showRendering $rendering" + } + } + } + + val envString = object : ViewEnvironmentKey(String::class) { + override val default: String get() = "Not set" + } + + val outerViewFactory = DecorativeViewFactory( + type = OuterRendering::class, + map = { outer, env -> + val enhancedEnv = env + (envString to "Outer Updated environment") + Pair(outer.wrapped, enhancedEnv) + }, + initializeView = { + val outerRendering = getRendering() + events += "outer initializeView $outerRendering ${environment!![envString]}" + showFirstRendering() + events += "exit outer initializeView" + } + ) + + val wayOutViewFactory = DecorativeViewFactory( + type = WayOutRendering::class, + map = { wayOut, env -> + val enhancedEnv = env + (envString to "Way Out Updated environment") + Pair(wayOut.wrapped, enhancedEnv) + }, + initializeView = { + val wayOutRendering = getRendering() + events += "way out initializeView $wayOutRendering ${environment!![envString]}" + showFirstRendering() + events += "exit way out initializeView" + } + ) + val viewRegistry = ViewRegistry(innerViewFactory, outerViewFactory, wayOutViewFactory) + val viewEnvironment = ViewEnvironment(mapOf(ViewRegistry to viewRegistry)) + + viewRegistry.buildView( + WayOutRendering("way out", OuterRendering("outer", InnerRendering("inner"))), + viewEnvironment, + instrumentation.context + ) + + assertThat(events).containsExactly( + "way out initializeView " + + "WayOutRendering(wayOutData=way out, wrapped=" + + "OuterRendering(outerData=outer, wrapped=" + + "InnerRendering(innerData=inner))) " + + "Way Out Updated environment", + "outer initializeView " + + "OuterRendering(outerData=outer, wrapped=" + + "InnerRendering(innerData=inner)) " + + "Outer Updated environment", + "inner showRendering InnerRendering(innerData=inner)", + "exit outer initializeView", + "exit way out initializeView" + ) + } + @Test fun subsequent_showRendering_calls_wrapped_showRendering() { val events = mutableListOf() @@ -148,6 +222,10 @@ internal class DecorativeViewFactoryTest { val outerData: String, val wrapped: InnerRendering ) + private data class WayOutRendering( + val wayOutData: String, + val wrapped: OuterRendering + ) private class InnerView(context: Context) : View(context) } From b93a5227db27033886329cd24ee39f1b3b0e1286 Mon Sep 17 00:00:00 2001 From: Ray Ryan Date: Wed, 8 Dec 2021 17:15:49 -0800 Subject: [PATCH 2/2] Replaces *.initializeView with *.viewStarter Fixes #597. It used to be the case that every `DecorativeViewFactory` called `showRendering()` twice (#397). We fixed that (we thought) by introducing the `initializeView` lambda to `ViewRegistry.buildView` and `DecorativeViewFactory` (#408). Unfortunately, that fix botched recursion. Individual `DecorativeViewFactory` instances work fine, but if you wrap them you still get one `showRendering` call from each. Worse, upstream `initializeView` lambdas are clobbered by immediately downstream ones. e.g., when a `WorkflowViewStub` shows a `DecorativeViewFactory`, the `WorkflowLifecycleRunner.installOn` call in the former is clobbered. The fix is to completely decouple building a view from from this kind of initialization. `ViewRegistry.buildView` and its wrappers no longer try to call `showRendering` at all. Instead the caller of `buildView` (mostly `WorkflowViewStub`) is reponsible for immediately calling `View.start` on the new `View`. `View.start` makes the initial `showRendering` call that formerly was the job of `ViewFactory.buildView` -- the factory builds the view, and the container turns the key. Since `View.start` is called only after all wrapped `ViewFactory.buildView` functions have executed, we're certain it will only happen once. Of course we still need the ability to customize view initialization via wrapping, especially to invoke `WorkflowLifecycleOwner.installOn`. To accomodate that, the function that `View.start` executes can be wrapped via the new `viewStarter` argument to `ViewRegistry.buildView` and `DecorativeViewFactory`, which replaces `initializeView`. This required a pretty thorough overhaul of `ViewShowRendering.kt` The `ViewShowRenderingTag` that it hangs off of a view tag is renamed `WorkflowViewState`, and extracted to a separate file. `WorkflowViewState` is a sealed class with two implementations (`New` and `Started`) to help us enforce the order of the `ViewRegistry.buildView`, `View.bindShowRendering`, `View.start` and `View.showRendering` calls. --- .../workflow1/ui/compose/WorkflowRendering.kt | 3 + .../ui/backstack/BackStackContainer.kt | 9 +- .../workflow1/ui/modal/ModalViewContainer.kt | 2 + workflow-ui/core-android/api/core-android.api | 70 ++++++--- .../workflow1/ui/DecorativeViewFactoryTest.kt | 78 +++++----- .../workflow1/ui/DecorativeViewFactory.kt | 24 ++- .../com/squareup/workflow1/ui/ViewRegistry.kt | 78 +++++----- .../workflow1/ui/ViewShowRendering.kt | 142 ++++++++++-------- .../workflow1/ui/WorkflowViewState.kt | 58 +++++++ .../squareup/workflow1/ui/WorkflowViewStub.kt | 8 +- .../core-android/src/main/res/values/ids.xml | 2 +- .../workflow1/ui/CompositeViewRegistryTest.kt | 2 +- .../squareup/workflow1/ui/TestViewFactory.kt | 4 +- 13 files changed, 286 insertions(+), 194 deletions(-) create mode 100644 workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/WorkflowViewState.kt diff --git a/workflow-ui/compose/src/main/java/com/squareup/workflow1/ui/compose/WorkflowRendering.kt b/workflow-ui/compose/src/main/java/com/squareup/workflow1/ui/compose/WorkflowRendering.kt index 7b086a53a..22e33f07e 100644 --- a/workflow-ui/compose/src/main/java/com/squareup/workflow1/ui/compose/WorkflowRendering.kt +++ b/workflow-ui/compose/src/main/java/com/squareup/workflow1/ui/compose/WorkflowRendering.kt @@ -27,6 +27,7 @@ import com.squareup.workflow1.ui.WorkflowViewStub import com.squareup.workflow1.ui.getFactoryForRendering import com.squareup.workflow1.ui.getShowRendering import com.squareup.workflow1.ui.showRendering +import com.squareup.workflow1.ui.start import kotlin.reflect.KClass /** @@ -184,6 +185,8 @@ private fun ViewFactory.asComposeViewFactory() = // we don't have access to that. originalFactory.buildView(rendering, viewEnvironment, context, container = null) .also { view -> + view.start() + // Mirrors the check done in ViewRegistry.buildView. checkNotNull(view.getShowRendering()) { "View.bindShowRendering should have been called for $view, typically by the " + diff --git a/workflow-ui/container-android/src/main/java/com/squareup/workflow1/ui/backstack/BackStackContainer.kt b/workflow-ui/container-android/src/main/java/com/squareup/workflow1/ui/backstack/BackStackContainer.kt index 72bcd19d3..3fdf611e7 100644 --- a/workflow-ui/container-android/src/main/java/com/squareup/workflow1/ui/backstack/BackStackContainer.kt +++ b/workflow-ui/container-android/src/main/java/com/squareup/workflow1/ui/backstack/BackStackContainer.kt @@ -32,8 +32,8 @@ import com.squareup.workflow1.ui.canShowRendering import com.squareup.workflow1.ui.compatible import com.squareup.workflow1.ui.container.R import com.squareup.workflow1.ui.getRendering -import com.squareup.workflow1.ui.showFirstRendering import com.squareup.workflow1.ui.showRendering +import com.squareup.workflow1.ui.start /** * A container view that can display a stream of [BackStackScreen] instances. @@ -103,11 +103,12 @@ public open class BackStackContainer @JvmOverloads constructor( initialViewEnvironment = environment, contextForNewView = this.context, container = this, - initializeView = { - WorkflowLifecycleOwner.installOn(this) - showFirstRendering() + viewStarter = { view, doStart -> + WorkflowLifecycleOwner.installOn(view) + doStart() } ) + newView.start() viewStateCache.update(named.backStack, oldViewMaybe, newView) val popped = currentRendering?.backStack?.any { compatible(it, named.top) } == true diff --git a/workflow-ui/container-android/src/main/java/com/squareup/workflow1/ui/modal/ModalViewContainer.kt b/workflow-ui/container-android/src/main/java/com/squareup/workflow1/ui/modal/ModalViewContainer.kt index 6df9057e1..9be5455d4 100644 --- a/workflow-ui/container-android/src/main/java/com/squareup/workflow1/ui/modal/ModalViewContainer.kt +++ b/workflow-ui/container-android/src/main/java/com/squareup/workflow1/ui/modal/ModalViewContainer.kt @@ -20,6 +20,7 @@ import com.squareup.workflow1.ui.buildView import com.squareup.workflow1.ui.modal.ModalViewContainer.Companion.binding import com.squareup.workflow1.ui.onBackPressedDispatcherOwnerOrNull import com.squareup.workflow1.ui.showRendering +import com.squareup.workflow1.ui.start import kotlin.reflect.KClass /** @@ -72,6 +73,7 @@ public open class ModalViewContainer @JvmOverloads constructor( container = this ) .apply { + start() // If the modal's root view has no backPressedHandler, add a no-op one to // ensure that the `onBackPressed` call below will not leak up to handlers // that should be blocked by this modal session. diff --git a/workflow-ui/core-android/api/core-android.api b/workflow-ui/core-android/api/core-android.api index 4cc208b36..41a55c388 100644 --- a/workflow-ui/core-android/api/core-android.api +++ b/workflow-ui/core-android/api/core-android.api @@ -24,10 +24,10 @@ public final class com/squareup/workflow1/ui/BuilderViewFactory : com/squareup/w } public final class com/squareup/workflow1/ui/DecorativeViewFactory : com/squareup/workflow1/ui/ViewFactory { - public fun (Lkotlin/reflect/KClass;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function4;)V - public synthetic fun (Lkotlin/reflect/KClass;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function4;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public fun (Lkotlin/reflect/KClass;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function4;)V - public synthetic fun (Lkotlin/reflect/KClass;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function4;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun (Lkotlin/reflect/KClass;Lkotlin/jvm/functions/Function1;Lcom/squareup/workflow1/ui/ViewStarter;Lkotlin/jvm/functions/Function4;)V + public synthetic fun (Lkotlin/reflect/KClass;Lkotlin/jvm/functions/Function1;Lcom/squareup/workflow1/ui/ViewStarter;Lkotlin/jvm/functions/Function4;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun (Lkotlin/reflect/KClass;Lkotlin/jvm/functions/Function2;Lcom/squareup/workflow1/ui/ViewStarter;Lkotlin/jvm/functions/Function4;)V + public synthetic fun (Lkotlin/reflect/KClass;Lkotlin/jvm/functions/Function2;Lcom/squareup/workflow1/ui/ViewStarter;Lkotlin/jvm/functions/Function4;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public fun buildView (Ljava/lang/Object;Lcom/squareup/workflow1/ui/ViewEnvironment;Landroid/content/Context;Landroid/view/ViewGroup;)Landroid/view/View; public fun getType ()Lkotlin/reflect/KClass; } @@ -51,21 +51,6 @@ public final class com/squareup/workflow1/ui/LayoutRunnerViewFactory : com/squar public fun getType ()Lkotlin/reflect/KClass; } -public final class com/squareup/workflow1/ui/ShowRenderingTag { - public fun (Ljava/lang/Object;Lcom/squareup/workflow1/ui/ViewEnvironment;Lkotlin/jvm/functions/Function2;)V - public final fun component1 ()Ljava/lang/Object; - public final fun component2 ()Lcom/squareup/workflow1/ui/ViewEnvironment; - public final fun component3 ()Lkotlin/jvm/functions/Function2; - public final fun copy (Ljava/lang/Object;Lcom/squareup/workflow1/ui/ViewEnvironment;Lkotlin/jvm/functions/Function2;)Lcom/squareup/workflow1/ui/ShowRenderingTag; - public static synthetic fun copy$default (Lcom/squareup/workflow1/ui/ShowRenderingTag;Ljava/lang/Object;Lcom/squareup/workflow1/ui/ViewEnvironment;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lcom/squareup/workflow1/ui/ShowRenderingTag; - public fun equals (Ljava/lang/Object;)Z - public final fun getEnvironment ()Lcom/squareup/workflow1/ui/ViewEnvironment; - public final fun getShowRendering ()Lkotlin/jvm/functions/Function2; - public final fun getShowing ()Ljava/lang/Object; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - public final class com/squareup/workflow1/ui/SnapshotParcelsKt { public static final fun toSnapshot (Landroid/os/Parcelable;)Lcom/squareup/workflow1/Snapshot; } @@ -124,21 +109,24 @@ public final class com/squareup/workflow1/ui/ViewRegistry$Companion : com/square public final class com/squareup/workflow1/ui/ViewRegistryKt { public static final fun ViewRegistry ()Lcom/squareup/workflow1/ui/ViewRegistry; public static final fun ViewRegistry ([Lcom/squareup/workflow1/ui/ViewFactory;)Lcom/squareup/workflow1/ui/ViewRegistry; - 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; - 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; + public static final fun buildView (Lcom/squareup/workflow1/ui/ViewRegistry;Ljava/lang/Object;Lcom/squareup/workflow1/ui/ViewEnvironment;Landroid/content/Context;Landroid/view/ViewGroup;Lcom/squareup/workflow1/ui/ViewStarter;)Landroid/view/View; + 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;Lcom/squareup/workflow1/ui/ViewStarter;ILjava/lang/Object;)Landroid/view/View; public static final fun getFactoryForRendering (Lcom/squareup/workflow1/ui/ViewRegistry;Ljava/lang/Object;)Lcom/squareup/workflow1/ui/ViewFactory; public static final fun plus (Lcom/squareup/workflow1/ui/ViewRegistry;Lcom/squareup/workflow1/ui/ViewFactory;)Lcom/squareup/workflow1/ui/ViewRegistry; public static final fun plus (Lcom/squareup/workflow1/ui/ViewRegistry;Lcom/squareup/workflow1/ui/ViewRegistry;)Lcom/squareup/workflow1/ui/ViewRegistry; - public static final fun showFirstRendering (Landroid/view/View;)V } public final class com/squareup/workflow1/ui/ViewShowRenderingKt { public static final fun bindShowRendering (Landroid/view/View;Ljava/lang/Object;Lcom/squareup/workflow1/ui/ViewEnvironment;Lkotlin/jvm/functions/Function2;)V public static final fun canShowRendering (Landroid/view/View;Ljava/lang/Object;)Z public static final fun getEnvironment (Landroid/view/View;)Lcom/squareup/workflow1/ui/ViewEnvironment; - public static final fun getRendering (Landroid/view/View;)Ljava/lang/Object; public static final fun getShowRendering (Landroid/view/View;)Lkotlin/jvm/functions/Function2; public static final fun showRendering (Landroid/view/View;Ljava/lang/Object;Lcom/squareup/workflow1/ui/ViewEnvironment;)V + public static final fun start (Landroid/view/View;)V +} + +public abstract interface class com/squareup/workflow1/ui/ViewStarter { + public abstract fun startView (Landroid/view/View;Lkotlin/jvm/functions/Function0;)V } public final class com/squareup/workflow1/ui/WorkflowLayout : android/widget/FrameLayout { @@ -149,6 +137,42 @@ public final class com/squareup/workflow1/ui/WorkflowLayout : android/widget/Fra public static synthetic fun start$default (Lcom/squareup/workflow1/ui/WorkflowLayout;Lkotlinx/coroutines/flow/Flow;Lcom/squareup/workflow1/ui/ViewEnvironment;ILjava/lang/Object;)V } +public abstract class com/squareup/workflow1/ui/WorkflowViewState { + public abstract fun getEnvironment ()Lcom/squareup/workflow1/ui/ViewEnvironment; + public abstract fun getShowRendering ()Lkotlin/jvm/functions/Function2; +} + +public final class com/squareup/workflow1/ui/WorkflowViewState$New : com/squareup/workflow1/ui/WorkflowViewState { + public fun (Ljava/lang/Object;Lcom/squareup/workflow1/ui/ViewEnvironment;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function1;)V + public synthetic fun (Ljava/lang/Object;Lcom/squareup/workflow1/ui/ViewEnvironment;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function1;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component2 ()Lcom/squareup/workflow1/ui/ViewEnvironment; + public final fun component3 ()Lkotlin/jvm/functions/Function2; + public final fun component4 ()Lkotlin/jvm/functions/Function1; + public final fun copy (Ljava/lang/Object;Lcom/squareup/workflow1/ui/ViewEnvironment;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function1;)Lcom/squareup/workflow1/ui/WorkflowViewState$New; + public static synthetic fun copy$default (Lcom/squareup/workflow1/ui/WorkflowViewState$New;Ljava/lang/Object;Lcom/squareup/workflow1/ui/ViewEnvironment;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lcom/squareup/workflow1/ui/WorkflowViewState$New; + public fun equals (Ljava/lang/Object;)Z + public fun getEnvironment ()Lcom/squareup/workflow1/ui/ViewEnvironment; + public fun getShowRendering ()Lkotlin/jvm/functions/Function2; + public synthetic fun getShowing ()Ljava/lang/Object; + public final fun getStarter ()Lkotlin/jvm/functions/Function1; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class com/squareup/workflow1/ui/WorkflowViewState$Started : com/squareup/workflow1/ui/WorkflowViewState { + public fun (Ljava/lang/Object;Lcom/squareup/workflow1/ui/ViewEnvironment;Lkotlin/jvm/functions/Function2;)V + public final fun component2 ()Lcom/squareup/workflow1/ui/ViewEnvironment; + public final fun component3 ()Lkotlin/jvm/functions/Function2; + public final fun copy (Ljava/lang/Object;Lcom/squareup/workflow1/ui/ViewEnvironment;Lkotlin/jvm/functions/Function2;)Lcom/squareup/workflow1/ui/WorkflowViewState$Started; + public static synthetic fun copy$default (Lcom/squareup/workflow1/ui/WorkflowViewState$Started;Ljava/lang/Object;Lcom/squareup/workflow1/ui/ViewEnvironment;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lcom/squareup/workflow1/ui/WorkflowViewState$Started; + public fun equals (Ljava/lang/Object;)Z + public fun getEnvironment ()Lcom/squareup/workflow1/ui/ViewEnvironment; + public fun getShowRendering ()Lkotlin/jvm/functions/Function2; + public synthetic fun getShowing ()Ljava/lang/Object; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + public final class com/squareup/workflow1/ui/WorkflowViewStub : android/view/View { public fun (Landroid/content/Context;)V public fun (Landroid/content/Context;Landroid/util/AttributeSet;)V diff --git a/workflow-ui/core-android/src/androidTest/java/com/squareup/workflow1/ui/DecorativeViewFactoryTest.kt b/workflow-ui/core-android/src/androidTest/java/com/squareup/workflow1/ui/DecorativeViewFactoryTest.kt index 9999e4d57..e67359fc4 100644 --- a/workflow-ui/core-android/src/androidTest/java/com/squareup/workflow1/ui/DecorativeViewFactoryTest.kt +++ b/workflow-ui/core-android/src/androidTest/java/com/squareup/workflow1/ui/DecorativeViewFactoryTest.kt @@ -11,7 +11,7 @@ import org.junit.Test internal class DecorativeViewFactoryTest { private val instrumentation = InstrumentationRegistry.getInstrumentation() - @Test fun initializeView_is_only_call_to_showRendering() { + @Test fun viewStarter_is_only_call_to_showRendering() { val events = mutableListOf() val innerViewFactory = object : ViewFactory { @@ -38,27 +38,26 @@ internal class DecorativeViewFactoryTest { val enhancedEnv = env + (envString to "Updated environment") Pair(outer.wrapped, enhancedEnv) }, - initializeView = { - val outerRendering = getRendering() - events += "initializeView $outerRendering ${environment!![envString]}" - showFirstRendering() - events += "exit initializeView" + viewStarter = { view, doStart -> + events += "viewStarter ${view.getRendering()} ${view.environment!![envString]}" + doStart() + events += "exit viewStarter" } ) - val viewRegistry = ViewRegistry(innerViewFactory) + val viewRegistry = ViewRegistry(innerViewFactory, outerViewFactory) val viewEnvironment = ViewEnvironment(mapOf(ViewRegistry to viewRegistry)) - outerViewFactory.buildView( + viewRegistry.buildView( OuterRendering("outer", InnerRendering("inner")), viewEnvironment, instrumentation.context - ) + ).start() assertThat(events).containsExactly( - "initializeView OuterRendering(outerData=outer, wrapped=InnerRendering(innerData=inner)) " + + "viewStarter OuterRendering(outerData=outer, wrapped=InnerRendering(innerData=inner)) " + "Updated environment", "inner showRendering InnerRendering(innerData=inner)", - "exit initializeView" + "exit viewStarter" ) } @@ -86,14 +85,14 @@ internal class DecorativeViewFactoryTest { innerShowRendering(outerRendering.wrapped, env) } ) - val viewRegistry = ViewRegistry(innerViewFactory) + val viewRegistry = ViewRegistry(innerViewFactory, outerViewFactory) val viewEnvironment = ViewEnvironment(mapOf(ViewRegistry to viewRegistry)) - outerViewFactory.buildView( + viewRegistry.buildView( OuterRendering("outer", InnerRendering("inner")), viewEnvironment, instrumentation.context - ) + ).start() assertThat(events).containsExactly( "doShowRendering OuterRendering(outerData=outer, wrapped=InnerRendering(innerData=inner))", @@ -126,28 +125,27 @@ internal class DecorativeViewFactoryTest { val outerViewFactory = DecorativeViewFactory( type = OuterRendering::class, map = { outer, env -> - val enhancedEnv = env + (envString to "Outer Updated environment") + val enhancedEnv = env + (envString to "Outer Updated environment" + + " SHOULD NOT SEE THIS! It will be clobbered by WayOutRendering") Pair(outer.wrapped, enhancedEnv) }, - initializeView = { - val outerRendering = getRendering() - events += "outer initializeView $outerRendering ${environment!![envString]}" - showFirstRendering() - events += "exit outer initializeView" + viewStarter = { view, doStart -> + events += "outer viewStarter ${view.getRendering()} ${view.environment!![envString]}" + doStart() + events += "exit outer viewStarter" } ) val wayOutViewFactory = DecorativeViewFactory( type = WayOutRendering::class, map = { wayOut, env -> - val enhancedEnv = env + (envString to "Way Out Updated environment") + val enhancedEnv = env + (envString to "Way Out Updated environment triumphs over all") Pair(wayOut.wrapped, enhancedEnv) }, - initializeView = { - val wayOutRendering = getRendering() - events += "way out initializeView $wayOutRendering ${environment!![envString]}" - showFirstRendering() - events += "exit way out initializeView" + viewStarter = { view, doStart -> + events += "way out viewStarter ${view.getRendering()} ${view.environment!![envString]}" + doStart() + events += "exit way out viewStarter" } ) val viewRegistry = ViewRegistry(innerViewFactory, outerViewFactory, wayOutViewFactory) @@ -157,21 +155,26 @@ internal class DecorativeViewFactoryTest { WayOutRendering("way out", OuterRendering("outer", InnerRendering("inner"))), viewEnvironment, instrumentation.context - ) + ).start() assertThat(events).containsExactly( - "way out initializeView " + + "way out viewStarter " + "WayOutRendering(wayOutData=way out, wrapped=" + "OuterRendering(outerData=outer, wrapped=" + "InnerRendering(innerData=inner))) " + - "Way Out Updated environment", - "outer initializeView " + + "Way Out Updated environment triumphs over all", + "outer viewStarter " + + // Notice that both the initial rendering and the ViewEnvironment are stomped by + // the outermost wrapper before inners are invoked. Could try to give + // the inner wrapper access to the rendering it expected, but there are no + // use cases and it trashes the API. + "WayOutRendering(wayOutData=way out, wrapped=" + "OuterRendering(outerData=outer, wrapped=" + - "InnerRendering(innerData=inner)) " + - "Outer Updated environment", + "InnerRendering(innerData=inner))) " + + "Way Out Updated environment triumphs over all", "inner showRendering InnerRendering(innerData=inner)", - "exit outer initializeView", - "exit way out initializeView" + "exit outer viewStarter", + "exit way out viewStarter" ) } @@ -199,14 +202,14 @@ internal class DecorativeViewFactoryTest { innerShowRendering(outerRendering.wrapped, env) } ) - val viewRegistry = ViewRegistry(innerViewFactory) + val viewRegistry = ViewRegistry(innerViewFactory, outerViewFactory) val viewEnvironment = ViewEnvironment(mapOf(ViewRegistry to viewRegistry)) - val view = outerViewFactory.buildView( + val view = viewRegistry.buildView( OuterRendering("out1", InnerRendering("in1")), viewEnvironment, instrumentation.context - ) + ).apply { start() } events.clear() view.showRendering(OuterRendering("out2", InnerRendering("in2")), viewEnvironment) @@ -222,6 +225,7 @@ internal class DecorativeViewFactoryTest { val outerData: String, val wrapped: InnerRendering ) + private data class WayOutRendering( val wayOutData: String, val wrapped: OuterRendering diff --git a/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/DecorativeViewFactory.kt b/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/DecorativeViewFactory.kt index 4f24ca325..6e89b283b 100644 --- a/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/DecorativeViewFactory.kt +++ b/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/DecorativeViewFactory.kt @@ -61,9 +61,9 @@ import kotlin.reflect.KClass * by DecorativeViewFactory( * type = WithTutorialTips::class, * map = { withTips -> withTips.wrapped }, - * initializeView = { - * TutorialTipRunner.run(this) - * showFirstRendering>() + * viewStarter = { view, doStart -> + * TutorialTipRunner.run(view) + * doStart() * } * ) * @@ -100,10 +100,9 @@ import kotlin.reflect.KClass * @param map called to convert instances of [OuterT] to [InnerT], and to * allow [ViewEnvironment] to be transformed. * - * @param initializeView Optional function invoked immediately after the [View] is - * created (that is, immediately after the call to [ViewFactory.buildView]). - * [showRendering], [getRendering] and [environment] are all available when this is called. - * Defaults to a call to [View.showFirstRendering]. + * @param viewStarter An optional wrapper for the function invoked when [View.start] + * is called, allowing for last second initialization of a newly built [View]. + * See [ViewStarter] for details. * * @param doShowRendering called to apply the [ViewShowRendering] function for * [InnerT], allowing pre- and post-processing. Default implementation simply @@ -113,7 +112,7 @@ import kotlin.reflect.KClass public class DecorativeViewFactory( override val type: KClass, private val map: (OuterT, ViewEnvironment) -> Pair, - private val initializeView: View.() -> Unit = { showFirstRendering() }, + private val viewStarter: ViewStarter? = null, private val doShowRendering: ( view: View, innerShowRendering: ViewShowRendering, @@ -131,7 +130,7 @@ public class DecorativeViewFactory( public constructor( type: KClass, map: (OuterT) -> InnerT, - initializeView: View.() -> Unit = { showFirstRendering() }, + viewStarter: ViewStarter? = null, doShowRendering: ( view: View, innerShowRendering: ViewShowRendering, @@ -143,7 +142,7 @@ public class DecorativeViewFactory( ) : this( type, map = { outer, viewEnvironment -> Pair(map(outer), viewEnvironment) }, - initializeView = initializeView, + viewStarter = viewStarter, doShowRendering = doShowRendering ) @@ -161,8 +160,7 @@ public class DecorativeViewFactory( processedInitialEnv, contextForNewView, container, - // Don't call showRendering yet, we need to wrap the function first. - initializeView = { } + viewStarter ) .also { view -> val innerShowRendering: ViewShowRendering = view.getShowRendering()!! @@ -171,8 +169,6 @@ public class DecorativeViewFactory( initialRendering, processedInitialEnv ) { rendering, env -> doShowRendering(view, innerShowRendering, rendering, env) } - - view.initializeView() } } } diff --git a/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/ViewRegistry.kt b/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/ViewRegistry.kt index 6fd84cf76..9fdfa3582 100644 --- a/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/ViewRegistry.kt +++ b/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/ViewRegistry.kt @@ -112,17 +112,6 @@ public fun ViewRegistry(): ViewRegistry = TypedViewRegistry() * [AndroidViewRendering.viewFactory], if there is one. Note that this means that a * compile time [AndroidViewRendering.viewFactory] binding can be overridden at runtime. * - * The returned view will have a - * [WorkflowLifecycleOwner][com.squareup.workflow1.ui.androidx.WorkflowLifecycleOwner] - * set on it. The returned view must EITHER: - * - * 1. Be attached at least once to ensure that the lifecycle eventually gets destroyed (because its - * parent is destroyed), or - * 2. Have its - * [WorkflowLifecycleOwner.destroyOnDetach][com.squareup.workflow1.ui.androidx.WorkflowLifecycleOwner.destroyOnDetach] - * called, which will either schedule the - * lifecycle to be destroyed if the view is attached, or destroy it immediately if it's detached. - * * @throws IllegalArgumentException if no factory can be find for type [RenderingT] */ @WorkflowUiExperimentalApi @@ -143,27 +132,16 @@ public fun * It is usually more convenient to use [WorkflowViewStub] or [DecorativeViewFactory] * than to call this method directly. * - * Finds a [ViewFactory] to create a [View] to display [initialRendering]. The new view - * can be updated via calls to [View.showRendering] -- that is, it is guaranteed that - * [bindShowRendering] has been called on this view. - * - * The returned view will have a - * [WorkflowLifecycleOwner][com.squareup.workflow1.ui.androidx.WorkflowLifecycleOwner] - * set on it. The returned view must EITHER: - * - * 1. Be attached at least once to ensure that the lifecycle eventually gets destroyed (because its - * parent is destroyed), or - * 2. Have its - * [WorkflowLifecycleOwner.destroyOnDetach][com.squareup.workflow1.ui.androidx.WorkflowLifecycleOwner.destroyOnDetach] - * called, which will either schedule the - * lifecycle to be destroyed if the view is attached, or destroy it immediately if it's detached. + * Finds a [ViewFactory] to create a [View] ready to display [initialRendering]. The caller + * is responsible for calling [View.start] on the new [View]. After that, + * [View.showRendering] can be used to update it with new renderings that + * are [compatible] with [initialRendering]. * - * @param initializeView Optional function invoked immediately after the [View] is - * created (that is, immediately after the call to [ViewFactory.buildView]). - * [showRendering], [getRendering] and [environment] are all available when this is called. - * Defaults to a call to [View.showFirstRendering]. + * @param viewStarter An optional wrapper for the function invoked when [View.start] + * is called, allowing for last second initialization of a newly built [View]. + * See [ViewStarter] for details. * - * @throws IllegalArgumentException if no factory can be find for type [RenderingT] + * @throws IllegalArgumentException if no factory can be found for type [RenderingT] * * @throws IllegalStateException if the matching [ViewFactory] fails to call * [View.bindShowRendering] when constructing the view @@ -174,19 +152,43 @@ public fun ViewRegistry.buildView( initialViewEnvironment: ViewEnvironment, contextForNewView: Context, container: ViewGroup? = null, - initializeView: View.() -> Unit = { showFirstRendering() } + viewStarter: ViewStarter? = null, ): View { return getFactoryForRendering(initialRendering).buildView( initialRendering, initialViewEnvironment, contextForNewView, container ).also { view -> - checkNotNull(view.showRenderingTag) { + checkNotNull(view.workflowViewStateOrNull) { "View.bindShowRendering should have been called for $view, typically by the " + "${ViewFactory::class.java.name} that created it." } - initializeView.invoke(view) + viewStarter?.let { givenStarter -> + val doStart = view.starter + view.starter = { newView -> + givenStarter.startView(newView) { doStart.invoke(newView) } + } + } } } +/** + * A wrapper for the function invoked when [View.start] is called, allowing for + * last second initialization of a newly built [View]. Provided via [ViewRegistry.buildView] + * or [DecorativeViewFactory.viewStarter]. + * + * While [View.getRendering] may be called from [startView], it is not safe to + * assume that the type of the rendering retrieved matches the type the view was + * originally built to display. [ViewFactories][ViewFactory] can be wrapped, and + * renderings can be mapped to other types. + */ +@WorkflowUiExperimentalApi +public fun interface ViewStarter { + /** Called from [View.start]. [doStart] must be invoked. */ + public fun startView( + view: View, + doStart: () -> Unit + ) +} + @WorkflowUiExperimentalApi public operator fun ViewRegistry.plus(binding: ViewFactory<*>): ViewRegistry = this + ViewRegistry(binding) @@ -194,13 +196,3 @@ public operator fun ViewRegistry.plus(binding: ViewFactory<*>): ViewRegistry = @WorkflowUiExperimentalApi public operator fun ViewRegistry.plus(other: ViewRegistry): ViewRegistry = CompositeViewRegistry(this, other) - -/** - * Default implementation for the `initializeView` argument of [ViewRegistry.buildView], - * and for [DecorativeViewFactory.initializeView]. Calls [showRendering] against - * [getRendering] and [environment]. - */ -@WorkflowUiExperimentalApi -public fun View.showFirstRendering() { - showRendering(getRendering()!!, environment!!) -} diff --git a/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/ViewShowRendering.kt b/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/ViewShowRendering.kt index bb8496689..9b9a9d295 100644 --- a/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/ViewShowRendering.kt +++ b/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/ViewShowRendering.kt @@ -1,6 +1,8 @@ package com.squareup.workflow1.ui import android.view.View +import com.squareup.workflow1.ui.WorkflowViewState.New +import com.squareup.workflow1.ui.WorkflowViewState.Started /** * Function attached to a view created by [ViewFactory], to allow it @@ -8,29 +10,22 @@ import android.view.View */ @WorkflowUiExperimentalApi public typealias ViewShowRendering = - (@UnsafeVariance RenderingT, ViewEnvironment) -> Unit + (@UnsafeVariance RenderingT, ViewEnvironment) -> Unit +// Unsafe because typealias ViewShowRendering is not supported, can't +// declare variance on a typealias. If I recall correctly. /** -` * View tag that holds the function to make the view show instances of [RenderingT], and - * the [current rendering][showing]. + * For use by implementations of [ViewFactory.buildView]. Establishes [showRendering] + * as the implementation of [View.showRendering] for the receiver, possibly replacing + * the existing one. * - * @param showing the current rendering. Used by [canShowRendering] to decide if the - * view can be updated with the next rendering. - */ -@WorkflowUiExperimentalApi -public data class ShowRenderingTag( - val showing: RenderingT, - val environment: ViewEnvironment, - val showRendering: ViewShowRendering -) - -/** - * Establishes [showRendering] as the implementation of [View.showRendering] - * for the receiver, possibly replacing the existing one. Likewise sets / updates - * the values returned by [View.getRendering] and [View.environment]. - * - * Intended for use by implementations of [ViewFactory.buildView]. + * - After this method is called, [View.start] must be called exactly + * once before [View.showRendering] can be called. + * - If this method is called again _after_ [View.start] (e.g. if a [View] is reused), + * the receiver is reset to its initialized state, and [View.start] must + * be called again. * + * @see ViewFactory * @see DecorativeViewFactory */ @WorkflowUiExperimentalApi @@ -39,31 +34,56 @@ public fun View.bindShowRendering( initialViewEnvironment: ViewEnvironment, showRendering: ViewShowRendering ) { - setTag( - R.id.view_show_rendering_function, - ShowRenderingTag(initialRendering, initialViewEnvironment, showRendering) - ) + workflowViewState = when (workflowViewStateOrNull) { + is New<*> -> New(initialRendering, initialViewEnvironment, showRendering, starter) + else -> New(initialRendering, initialViewEnvironment, showRendering) + } + + // Note that if there is already a `New<*>` tag, we have to take care to propagate + // the starter. Repeated calls happen whenever one ViewFactory delegates to another. + // + // - We render `NamedScreen(FooScreen())` + // - The view is built by `FooScreenFactory`, which calls `bindShowRendering()` + // - `NamedScreenFactory` invokes `FooScreenFactory.buildView`, and calls + // `bindShowRendering>()` on the view that `FooScreenFactory` built. +} + +/** + * Note that [WorkflowViewStub] calls this method for you. + * + * Makes the initial call to [View.showRendering], along with any wrappers that have been + * added via [ViewRegistry.buildView], or [DecorativeViewFactory.viewStarter]. + * + * - It is an error to call this method more than once. + * - It is an error to call [View.showRendering] without having called this method first. + */ +@WorkflowUiExperimentalApi +public fun View.start() { + val current = workflowViewStateAsNew + workflowViewState = Started(current.showing, current.environment, current.showRendering) + current.starter(this) } /** - * It is usually more convenient to use [WorkflowViewStub] than to call this method directly. + * Note that [WorkflowViewStub.showRendering] makes this check for you. * * True if this view is able to show [rendering]. * - * Returns `false` if [bindShowRendering] has not been called, so it is always safe to - * call this method. Otherwise returns the [compatibility][compatible] of the initial - * [rendering] and the new one. + * Returns `false` if [View.bindShowRendering] has not been called, so it is always safe to + * call this method. Otherwise returns the [compatibility][compatible] of the current + * [View.getRendering] and the new one. */ @WorkflowUiExperimentalApi public fun View.canShowRendering(rendering: Any): Boolean { - return getRendering()?.matches(rendering) == true + return getRendering()?.let { compatible(it, rendering) } == true } /** - * It is usually more convenient to use [WorkflowViewStub] than to call this method directly. + * It is usually more convenient to call [WorkflowViewStub.showRendering] + * than to call this method directly. * - * Sets the workflow rendering associated with this view, and displays it by - * invoking the [ViewShowRendering] function previously set by [bindShowRendering]. + * Shows [rendering] in this View by invoking the [ViewShowRendering] function + * previously set by [bindShowRendering]. * * @throws IllegalStateException if [bindShowRendering] has not been called. */ @@ -72,47 +92,42 @@ public fun View.showRendering( rendering: RenderingT, viewEnvironment: ViewEnvironment ) { - showRenderingTag - ?.let { tag -> - check(tag.showing.matches(rendering)) { - "Expected $this to be able to show rendering $rendering, but that did not match " + - "previous rendering ${tag.showing}. " + - "Consider using ${WorkflowViewStub::class.java.simpleName} to display arbitrary types." - } - - // Update the tag's rendering and viewEnvironment. - bindShowRendering(rendering, viewEnvironment, tag.showRendering) - // And do the actual showRendering work. - tag.showRendering.invoke(rendering, viewEnvironment) + workflowViewStateAsStarted.let { viewState -> + check(compatible(viewState.showing, rendering)) { + "Expected $this to be able to show rendering $rendering, but that did not match " + + "previous rendering ${viewState.showing}. " + + "Consider using WorkflowViewStub to display arbitrary types." } - ?: error( - "Expected $this to have a showRendering function to show $rendering. " + - "Perhaps it was not built by a ${ViewFactory::class.java.simpleName}, " + - "or perhaps the factory did not call View.bindShowRendering." - ) + + // Update the tag's rendering and viewEnvironment before calling + // the actual showRendering function. + workflowViewState = Started(rendering, viewEnvironment, viewState.showRendering) + viewState.showRendering.invoke(rendering, viewEnvironment) + } } /** - * Returns the most recent rendering shown by this view, or null if [bindShowRendering] - * has never been called. + * Returns the most recent rendering shown by this view cast to [RenderingT], + * or null if [bindShowRendering] has never been called. + * + * @throws ClassCastException if the current rendering is not of type [RenderingT] */ @WorkflowUiExperimentalApi -public fun View.getRendering(): RenderingT? { +public inline fun View.getRendering(): RenderingT? { // Can't use a val because of the parameter type. - @Suppress("UNCHECKED_CAST") - return when (val showing = showRenderingTag?.showing) { + return when (val showing = workflowViewStateOrNull?.showing) { null -> null else -> showing as RenderingT } } /** - * Returns the most recent [ViewEnvironment] that apply to this view, or null if [bindShowRendering] + * Returns the most recent [ViewEnvironment] applied to this view, or null if [bindShowRendering] * has never been called. */ @WorkflowUiExperimentalApi public val View.environment: ViewEnvironment? - get() = showRenderingTag?.environment + get() = workflowViewStateOrNull?.environment /** * Returns the function set by the most recent call to [bindShowRendering], or null @@ -120,17 +135,12 @@ public val View.environment: ViewEnvironment? */ @WorkflowUiExperimentalApi public fun View.getShowRendering(): ViewShowRendering? { - return showRenderingTag?.showRendering + return workflowViewStateOrNull?.showRendering } -/** - * Returns the [ShowRenderingTag] established by the last call to [View.bindShowRendering], - * or null if that method has never been called. - */ -@WorkflowUiExperimentalApi -@PublishedApi -internal val View.showRenderingTag: ShowRenderingTag<*>? - get() = getTag(R.id.view_show_rendering_function) as? ShowRenderingTag<*> - @WorkflowUiExperimentalApi -private fun Any.matches(other: Any) = compatible(this, other) +internal var View.starter: (View) -> Unit + get() = workflowViewStateAsNew.starter + set(value) { + workflowViewState = workflowViewStateAsNew.copy(starter = value) + } diff --git a/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/WorkflowViewState.kt b/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/WorkflowViewState.kt new file mode 100644 index 000000000..a0d42a28c --- /dev/null +++ b/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/WorkflowViewState.kt @@ -0,0 +1,58 @@ +package com.squareup.workflow1.ui + +import android.view.View +import com.squareup.workflow1.ui.WorkflowViewState.New +import com.squareup.workflow1.ui.WorkflowViewState.Started + +/** + * [View tag][View.setTag] that holds the functions and state backing [View.showRendering], etc. + */ +@WorkflowUiExperimentalApi +@PublishedApi +internal sealed class WorkflowViewState { + @PublishedApi + internal abstract val showing: RenderingT + abstract val environment: ViewEnvironment + abstract val showRendering: ViewShowRendering + + /** [bindShowRendering] has been called, [start] has not. */ + data class New( + override val showing: RenderingT, + override val environment: ViewEnvironment, + override val showRendering: ViewShowRendering, + + val starter: (View) -> Unit = { view -> + view.showRendering(view.getRendering()!!, view.environment!!) + } + ) : WorkflowViewState() + + /** [start] has been called. It's safe to call [showRendering] now. */ + data class Started( + override val showing: RenderingT, + override val environment: ViewEnvironment, + override val showRendering: ViewShowRendering + ) : WorkflowViewState() +} + +@WorkflowUiExperimentalApi +@PublishedApi +internal val View.workflowViewStateOrNull: WorkflowViewState<*>? + get() = getTag(R.id.workflow_ui_view_state) as? WorkflowViewState<*> + +@WorkflowUiExperimentalApi +internal var View.workflowViewState: WorkflowViewState<*> + get() = workflowViewStateOrNull ?: error( + "Expected $this to have been built by a ViewFactory. " + + "Perhaps the factory did not call View.bindShowRendering." + ) + set(value) = setTag(R.id.workflow_ui_view_state, value) + +@WorkflowUiExperimentalApi internal val View.workflowViewStateAsNew: New<*> + get() = workflowViewState as? New<*> ?: error( + "Expected $this to be un-started, but View.start() has been called" + ) + +@WorkflowUiExperimentalApi internal val View.workflowViewStateAsStarted: Started<*> + get() = workflowViewState as? Started<*> ?: error( + "Expected $this to have been started, but View.start() has not been called" + ) diff --git a/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/WorkflowViewStub.kt b/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/WorkflowViewStub.kt index a290e2149..b5056acbb 100644 --- a/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/WorkflowViewStub.kt +++ b/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/WorkflowViewStub.kt @@ -225,12 +225,14 @@ public class WorkflowViewStub @JvmOverloads constructor( viewEnvironment, parent.context, parent, - initializeView = { - WorkflowLifecycleOwner.installOn(this) - showFirstRendering() + viewStarter = { view, doStart -> + WorkflowLifecycleOwner.installOn(view) + doStart() } ) .also { newView -> + newView.start() + if (inflatedId != NO_ID) newView.id = inflatedId if (updatesVisibility) newView.visibility = visibility background?.let { newView.background = it } diff --git a/workflow-ui/core-android/src/main/res/values/ids.xml b/workflow-ui/core-android/src/main/res/values/ids.xml index 1e13c033c..e0f89acb6 100644 --- a/workflow-ui/core-android/src/main/res/values/ids.xml +++ b/workflow-ui/core-android/src/main/res/values/ids.xml @@ -1,7 +1,7 @@ - + diff --git a/workflow-ui/core-android/src/test/java/com/squareup/workflow1/ui/CompositeViewRegistryTest.kt b/workflow-ui/core-android/src/test/java/com/squareup/workflow1/ui/CompositeViewRegistryTest.kt index 2f4a605cf..dc8ab0765 100644 --- a/workflow-ui/core-android/src/test/java/com/squareup/workflow1/ui/CompositeViewRegistryTest.kt +++ b/workflow-ui/core-android/src/test/java/com/squareup/workflow1/ui/CompositeViewRegistryTest.kt @@ -6,7 +6,7 @@ import kotlin.reflect.KClass import kotlin.test.assertFailsWith @OptIn(WorkflowUiExperimentalApi::class) -class CompositeViewRegistryTest { +internal class CompositeViewRegistryTest { @Test fun `constructor throws on duplicates`() { val fooBarRegistry = TestRegistry(setOf(FooRendering::class, BarRendering::class)) diff --git a/workflow-ui/core-android/src/test/java/com/squareup/workflow1/ui/TestViewFactory.kt b/workflow-ui/core-android/src/test/java/com/squareup/workflow1/ui/TestViewFactory.kt index a718275c8..569085a8b 100644 --- a/workflow-ui/core-android/src/test/java/com/squareup/workflow1/ui/TestViewFactory.kt +++ b/workflow-ui/core-android/src/test/java/com/squareup/workflow1/ui/TestViewFactory.kt @@ -25,8 +25,8 @@ internal class TestViewFactory(override val type: KClass) : ViewFact called = true return mock { on { - getTag(eq(com.squareup.workflow1.ui.R.id.view_show_rendering_function)) - } doReturn (ShowRenderingTag(initialRendering, initialViewEnvironment, { _, _ -> })) + getTag(eq(com.squareup.workflow1.ui.R.id.workflow_ui_view_state)) + } doReturn (WorkflowViewState.New(initialRendering, initialViewEnvironment, { _, _ -> })) } } }