Skip to content

Commit 8192497

Browse files
committed
Introduces ScreenViewFactory.forWrapper
Wrapping is finally as simple as it should be. Also moves `ScreenViewRunner` to a better home in `ScreenViewHolder.kt`.
1 parent a71b8a9 commit 8192497

File tree

7 files changed

+168
-193
lines changed

7 files changed

+168
-193
lines changed

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

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -99,10 +99,6 @@ public final class com/squareup/workflow1/ui/LayoutScreenViewFactory : com/squar
9999
public fun getType ()Lkotlin/reflect/KClass;
100100
}
101101

102-
public final class com/squareup/workflow1/ui/NamedScreenViewFactoryKt {
103-
public static final fun NamedScreenViewFactory ()Lcom/squareup/workflow1/ui/ScreenViewFactory;
104-
}
105-
106102
public final class com/squareup/workflow1/ui/NamedViewFactory : com/squareup/workflow1/ui/ViewFactory {
107103
public static final field INSTANCE Lcom/squareup/workflow1/ui/NamedViewFactory;
108104
public fun buildView (Lcom/squareup/workflow1/ui/Named;Lcom/squareup/workflow1/ui/ViewEnvironment;Landroid/content/Context;Landroid/view/ViewGroup;)Landroid/view/View;
@@ -157,6 +153,9 @@ public abstract interface class com/squareup/workflow1/ui/ScreenViewFactory : co
157153
}
158154

159155
public final class com/squareup/workflow1/ui/ScreenViewFactory$Companion {
156+
public final synthetic fun forWrapper (Lkotlin/jvm/functions/Function1;)Lcom/squareup/workflow1/ui/ScreenViewFactory;
157+
public final synthetic fun forWrapper (Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function4;)Lcom/squareup/workflow1/ui/ScreenViewFactory;
158+
public static synthetic fun forWrapper$default (Lcom/squareup/workflow1/ui/ScreenViewFactory$Companion;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function4;ILjava/lang/Object;)Lcom/squareup/workflow1/ui/ScreenViewFactory;
160159
public final synthetic fun fromCode (Lkotlin/jvm/functions/Function4;)Lcom/squareup/workflow1/ui/ScreenViewFactory;
161160
public final synthetic fun fromLayout (ILkotlin/jvm/functions/Function1;)Lcom/squareup/workflow1/ui/ScreenViewFactory;
162161
public final synthetic fun fromStaticLayout (I)Lcom/squareup/workflow1/ui/ScreenViewFactory;
@@ -185,7 +184,6 @@ public final class com/squareup/workflow1/ui/ScreenViewFactoryFinder$DefaultImpl
185184
public final class com/squareup/workflow1/ui/ScreenViewFactoryKt {
186185
public static final fun startShowing (Lcom/squareup/workflow1/ui/ScreenViewFactory;Lcom/squareup/workflow1/ui/Screen;Lcom/squareup/workflow1/ui/ViewEnvironment;Landroid/content/Context;Landroid/view/ViewGroup;Lcom/squareup/workflow1/ui/ViewStarter;)Lcom/squareup/workflow1/ui/ScreenViewHolder;
187186
public static synthetic fun startShowing$default (Lcom/squareup/workflow1/ui/ScreenViewFactory;Lcom/squareup/workflow1/ui/Screen;Lcom/squareup/workflow1/ui/ViewEnvironment;Landroid/content/Context;Landroid/view/ViewGroup;Lcom/squareup/workflow1/ui/ViewStarter;ILjava/lang/Object;)Lcom/squareup/workflow1/ui/ScreenViewHolder;
188-
public static final synthetic fun toUnwrappingViewFactory (Lcom/squareup/workflow1/ui/ScreenViewFactory;Lkotlin/jvm/functions/Function1;)Lcom/squareup/workflow1/ui/ScreenViewFactory;
189187
public static final synthetic fun toUnwrappingViewFactory (Lcom/squareup/workflow1/ui/ScreenViewFactory;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function4;)Lcom/squareup/workflow1/ui/ScreenViewFactory;
190188
public static final fun toViewFactory (Lcom/squareup/workflow1/ui/Screen;Lcom/squareup/workflow1/ui/ViewEnvironment;)Lcom/squareup/workflow1/ui/ScreenViewFactory;
191189
public static final fun viewBindingLayoutInflater (Landroid/content/Context;Landroid/view/ViewGroup;)Landroid/view/LayoutInflater;

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import android.view.ViewGroup
66
import kotlin.reflect.KClass
77

88
/**
9-
* **This will be deprecated in favor of [ScreenViewFactory.toUnwrappingViewFactory] very soon.**
9+
* **This will be deprecated in favor of [ScreenViewFactory.forWrapper] very soon.**
1010
*
1111
* A [ViewFactory] for [OuterT] that delegates view construction responsibilities
1212
* to the factory registered for [InnerT]. Makes it convenient for [OuterT] to wrap

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

Lines changed: 0 additions & 15 deletions
This file was deleted.

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

Lines changed: 121 additions & 143 deletions
Original file line numberDiff line numberDiff line change
@@ -13,25 +13,6 @@ import com.squareup.workflow1.ui.ScreenViewFactory.Companion.fromViewBinding
1313
@WorkflowUiExperimentalApi
1414
public typealias ViewBindingInflater<BindingT> = (LayoutInflater, ViewGroup?, Boolean) -> BindingT
1515

16-
/**
17-
* The function that updates a [View] instance built by a [ScreenViewFactory].
18-
* Each [ScreenViewRunner] instance is paired with the single [View] instance,
19-
* its neighbor in a [ScreenViewHolder].
20-
*
21-
* This is the interface you'll implement directly to update Android view code
22-
* from your [Screen] renderings. A [ScreenViewRunner] serves as the strategy
23-
* object of a [ScreenViewHolder] instantiated by a [ScreenViewFactory] -- the
24-
* runner provides the implementation for the holder's [ScreenViewHolder.show]
25-
* method.
26-
*/
27-
@WorkflowUiExperimentalApi
28-
public fun interface ScreenViewRunner<in ScreenT : Screen> {
29-
public fun showRendering(
30-
rendering: ScreenT,
31-
viewEnvironment: ViewEnvironment
32-
)
33-
}
34-
3516
/**
3617
* A [ViewRegistry.Entry] that can build Android [View] instances, along with functions
3718
* that can update them to display [Screen] renderings of a particular [type], bundled
@@ -165,6 +146,125 @@ public interface ScreenViewFactory<in ScreenT : Screen> : ViewRegistry.Entry<Scr
165146
buildView(initialRendering, initialEnvironment, context, container)
166147
}
167148
}
149+
150+
/**
151+
* Creates a [ScreenViewFactory] for [WrapperT] that finds and delegates to
152+
* the one for [WrappedT]. Allows [WrapperT] to wrap instances of [WrappedT]
153+
* to add information or behavior, without requiring wasteful wrapping in the view system.
154+
*
155+
* One general note: when creating a wrapper rendering, you're very likely to want it
156+
* to implement [Compatible], to ensure that checks made to update or replace a view
157+
* are based on the wrapped item. Each wrapper example below illustrates this.
158+
*
159+
* This a simpler variant of the like named function that takes three arguments, for
160+
* use when there is no need to manipulate the [ScreenViewHolder].
161+
*
162+
* ## Examples
163+
*
164+
* To make one rendering type an "alias" for another -- that is, to use the
165+
* same [ScreenViewFactory] to display it:
166+
*
167+
* class RealScreen(val data: String): AndroidScreen<RealScreen> {
168+
* override val viewFactory = fromLayout<RealScreen>(...)
169+
* }
170+
*
171+
* class AliasScreen(val similarData: String) : AndroidScreen<AliasScreen> {
172+
* override val viewFactory = forWrapper<AliasScreen, RealScreen> { aliasScreen ->
173+
* RealScreen(aliasScreen.similarData)
174+
* }
175+
* }
176+
*
177+
* To make one [Screen] type a wrapper for others:
178+
*
179+
* class Wrapper<W>(val wrapped: W: Screen) : AndroidScreen<Wrapper<W>>, Compatible {
180+
* override val compatibilityKey = Compatible.keyFor(wrapped)
181+
* override val viewFactory = ScreenViewFactory.forWrapper<Wrapper<W>, W> { it.wrapped }
182+
* }
183+
*
184+
* To make a wrapper that adds information to the [ViewEnvironment]:
185+
*
186+
* class ReverseNeutronFlowPolarity : ViewEnvironmentKey<Boolean>(Boolean::class) {
187+
* override val default = false
188+
* }
189+
*
190+
* class ReversePolarityScreen<W : Screen>(
191+
* val wrapped: W
192+
* ) : AndroidScreen<ReversePolarityScreen<W>>, Compatible {
193+
* override val compatibilityKey: String = Compatible.keyFor(wrapped)
194+
* override val viewFactory = forWrapper<OverrideNeutronFlow<W>, Screen> {
195+
* it.wrapped.withEnvironment(
196+
* Environment.EMPTY + (ReverseNeutronFlowPolarity to true)
197+
* )
198+
* }
199+
* }
200+
*
201+
* @param unwrap a function to extract [WrappedT] instances from [WrapperT]s.
202+
*/
203+
@WorkflowUiExperimentalApi
204+
public inline fun <
205+
reified WrapperT : Screen,
206+
WrappedT : Screen
207+
> forWrapper(
208+
crossinline unwrap: (wrapperScreen: WrapperT) -> WrappedT,
209+
): ScreenViewFactory<WrapperT> = forWrapper(
210+
unwrap = unwrap,
211+
beforeShowing = {}
212+
) { _, wrapper, e, showWrapper ->
213+
showWrapper(unwrap(wrapper), e)
214+
}
215+
216+
/**
217+
* Creates a [ScreenViewFactory] for [WrapperT] that finds and delegates to
218+
* the one for [WrappedT]. Allows [WrapperT] to wrap instances of [WrappedT]
219+
* to add information or behavior, without requiring wasteful wrapping in the view system.
220+
*
221+
* This fully featured variant of the function is able to initialize the freshly
222+
* created [ScreenViewHolder], and transform the wrapped [ScreenViewHolder.runner].
223+
*
224+
* To make a wrapper that customizes [View] initialization:
225+
*
226+
* class WithTutorialTips<W : Screen>(
227+
* val wrapped: W
228+
* ) : AndroidScreen<WithTutorialTips<W>>, Compatible {
229+
* override val compatibilityKey = Compatible.keyFor(wrapped)
230+
* override val viewFactory = forWrapper<WithTutorialTips<W>, W>(
231+
* unwrap = { it.wrapped },
232+
* beforeShowing = { TutorialTipRunner.initialize(it.view) },
233+
* showWrapperScreen = { _, wrapper, environment, showWrapper ->
234+
* showWrapper(unwrap(wrapper), environment)
235+
* }
236+
* )
237+
* }
238+
*
239+
* @param unwrap a function to extract [WrappedT] instances from [WrapperT]s.
240+
*
241+
* @param beforeShowing a function to be invoked immediately after a new [View] is built.
242+
*
243+
* @param showWrapperScreen a function to be invoked when an instance of [WrapperT] needs
244+
* to be shown in a [View] built to display instances of [WrappedT]. Allows
245+
* pre- and post-processing of the [View].
246+
*/
247+
@WorkflowUiExperimentalApi
248+
public inline fun <
249+
reified WrapperT : Screen,
250+
WrappedT : Screen
251+
> forWrapper(
252+
crossinline unwrap: (wrapperScreen: WrapperT) -> WrappedT,
253+
crossinline beforeShowing: (viewHolder: ScreenViewHolder<WrapperT>) -> Unit = {},
254+
crossinline showWrapperScreen: (
255+
view: View,
256+
wrapperScreen: WrapperT,
257+
environment: ViewEnvironment,
258+
showUnwrappedScreen: (WrappedT, ViewEnvironment) -> Unit
259+
) -> Unit,
260+
): ScreenViewFactory<WrapperT> =
261+
fromCode { initialRendering, initialEnvironment, context, container ->
262+
val wrappedFactory = unwrap(initialRendering).toViewFactory(initialEnvironment)
263+
val wrapperFactory = wrappedFactory.toUnwrappingViewFactory(unwrap, showWrapperScreen)
264+
wrapperFactory.buildView(
265+
initialRendering, initialEnvironment, context, container
266+
).also { beforeShowing(it) }
267+
}
168268
}
169269
}
170270

@@ -266,140 +366,18 @@ public fun <ScreenT : Screen> ScreenViewFactory<ScreenT>.startShowing(
266366
*/
267367
@WorkflowUiExperimentalApi
268368
public fun interface ViewStarter {
269-
/** Called from [View.start]. [doStart] must be invoked. */
369+
/** Called from [ScreenViewFactory.startShowing]. [doStart] must be invoked. */
270370
public fun startView(
271371
view: View,
272372
doStart: () -> Unit
273373
)
274374
}
275375

276-
/**
277-
* Transforms a [ScreenViewFactory] of [WrappedT] into one that can handle
278-
* instances of [WrapperT]. Allows [WrapperT] to wrap instances of [WrappedT]
279-
* to add information or behavior, without requiring wasteful wrapping in the view system.
280-
*
281-
* One general note: when creating a wrapper rendering, you're very likely to want it
282-
* to implement [Compatible], to ensure that checks made to update or replace a view
283-
* are based on the wrapped item. Each wrapper example below illustrates this.
284-
*
285-
* This a simpler variant of the like named function that takes two arguments, for
286-
* use when there is no need to customize the [view][ScreenViewHolder.view] or
287-
* the [environment][ScreenViewHolder.environment].
288-
*
289-
* ## Examples
290-
*
291-
* To make one rendering type an "alias" for another -- that is, to use the
292-
* same [ScreenViewFactory] to display it:
293-
*
294-
* class RealScreen(val data: String): Screen
295-
* object RealScreenViewFactory = ScreenViewFactory.fromLayout(...)
296-
*
297-
* class AliasScreen(val similarData: String) : Screen
298-
*
299-
* object AliasScreenViewFactory =
300-
* RealScreenViewFactory.toUnwrappingViewFactory<AliasScreen, RealScreen> { aliasScreen ->
301-
* RealScreen(aliasScreen.similarData)
302-
* }
303-
*
304-
* To make one rendering type a wrapper for others:
305-
*
306-
* class Wrapper<W>(val wrapped: W: Screen) : Screen, Compatible {
307-
* override val compatibilityKey = Compatible.keyFor(wrapped)
308-
* }
309-
*
310-
* fun <W : Screen> WrapperViewFactory() =
311-
* ScreenViewFactory.forBuiltView<Wrapper<W>> { wrapper, env, context, container ->
312-
* // Get the view factory of the wrapped screen.
313-
* wrapper.wrapped.toViewFactory(env)
314-
* // Transform it to factory that accepts Wrapper<W>
315-
* .toUnwrappingViewFactory<Wrapper<W>, W> { it.wrapped }
316-
* // Delegate to the transformed factory to build the view.
317-
* .buildView(wrapper, env, context, container)
318-
* }
319-
*
320-
* To make a wrapper that adds information to the [ViewEnvironment]:
321-
*
322-
* class NeutronFlowPolarity(val reversed: Boolean) {
323-
* companion object : ViewEnvironmentKey<NeutronFlowPolarity>(
324-
* NeutronFlowPolarity::class
325-
* ) {
326-
* override val default: NeutronFlowPolarity =
327-
* NeutronFlowPolarity(reversed = false)
328-
* }
329-
* }
330-
*
331-
* class OverrideNeutronFlow<W : Screen>(
332-
* val wrapped: W,
333-
* val polarity: NeutronFlowPolarity
334-
* ) : Screen, Compatible {
335-
* override val compatibilityKey: String = Compatible.keyFor(wrapped)
336-
* }
337-
*
338-
* fun <W : Screen> OverrideNeutronFlowViewFactory() =
339-
* ScreenViewFactory.forBuiltView<OverrideNeutronFlow<W>> { wrapper, env, context, container ->
340-
* // Get the view factory of the wrapped screen.
341-
* wrapper.wrapped.toViewFactory(env)
342-
* // Transform it to factory that accepts OverrideNeutronFlow<W>, by
343-
* // replacing the OverrideNeutronFlow<W> with an EnvironmentScreen<W>
344-
* .toUnwrappingViewFactory<OverrideNeutronFlow<W>, EnvironmentScreen<W>> {
345-
* it.wrapped.withEnvironment(
346-
* Environment.EMPTY + (NeutronFlowPolarity to it.polarity)
347-
* )
348-
* }
349-
* // Delegate to the transformed factory to build the view.
350-
* .buildView(wrapper, env, context, container)
351-
* }
352-
*
353-
* @param unwrap a function to extract [WrappedT] instances from [WrapperT]s.
354-
*/
355-
@WorkflowUiExperimentalApi
356-
public inline fun <
357-
reified WrapperT : Screen,
358-
WrappedT : Screen
359-
> ScreenViewFactory<WrappedT>.toUnwrappingViewFactory(
360-
crossinline unwrap: (wrapperScreen: WrapperT) -> WrappedT,
361-
): ScreenViewFactory<WrapperT> =
362-
toUnwrappingViewFactory(unwrap) { _, ws, e, su -> su(unwrap(ws), e) }
363-
364376
/**
365377
* Transforms a [ScreenViewFactory] of [WrappedT] into one that can handle
366378
* instances of [WrapperT].
367379
*
368-
* One general note: when creating a wrapper rendering, you're very likely to want it
369-
* to implement [Compatible], to ensure that checks made to update or replace a view
370-
* are based on the wrapped item. Each wrapper example below illustrates this.
371-
*
372-
* Also see the simpler variant of this function that takes only an [unwrap] argument.
373-
*
374-
* ## Example
375-
*
376-
* To make a wrapper that customizes [View] initialization:
377-
*
378-
* class WithTutorialTips<W : Screen>(val wrapped: W) : Screen, Compatible {
379-
* override val compatibilityKey = Compatible.keyFor(wrapped)
380-
* }
381-
*
382-
* fun <W: Screen> WithTutorialTipsFactory<W>() =
383-
* ScreenViewFactory.forBuiltView<WithTutorialTips<*>> = {
384-
* initialRendering, initialEnv, context, container ->
385-
* // Get the view factory of the wrapped screen.
386-
* initialRendering.wrapped.toViewFactory(initialEnv)
387-
* // Transform it to factory that accepts WithTutorialTips<W>
388-
* .toUnwrappingViewFactory<WithTutorialTips<W>, W>(
389-
* unwrap = { it.wrapped },
390-
* showWrapperScreen = { view, withTips, env, showUnwrapped ->
391-
* TutorialTipRunner.run(view)
392-
* showUnwrapped(withTips.wrapped, env)
393-
* }
394-
* // Delegate to the transformed factory to build the view.
395-
* .buildView(initialRendering, initialEnv, context, container)
396-
* }
397-
*
398-
* @param unwrap a function to extract [WrappedT] instances from [WrapperT]s.
399-
*
400-
* @param showWrapperScreen a function invoked when an instance of [WrapperT] needs
401-
* to be shown in a [View] built to display instances of [WrappedT]. Allows
402-
* pre- and post-processing of the [View].
380+
* It is usually more convenient to call [ScreenViewFactory.forWrapper].
403381
*/
404382
@WorkflowUiExperimentalApi
405383
public inline fun <

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

Lines changed: 2 additions & 1 deletion
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.ScreenViewFactory.Companion.forWrapper
34
import com.squareup.workflow1.ui.container.BackStackScreen
45
import com.squareup.workflow1.ui.container.BackStackScreenViewFactory
56
import com.squareup.workflow1.ui.container.BodyAndModalsContainer
@@ -70,7 +71,7 @@ public interface ScreenViewFactoryFinder {
7071
BodyAndModalsContainer as ScreenViewFactory<ScreenT>
7172
}
7273
?: (rendering as? NamedScreen<*>)?.let {
73-
NamedScreenViewFactory<ScreenT>() as ScreenViewFactory<ScreenT>
74+
forWrapper<NamedScreen<ScreenT>, ScreenT> { it.wrapped } as ScreenViewFactory<ScreenT>
7475
}
7576
?: (rendering as? EnvironmentScreen<*>)?.let {
7677
EnvironmentScreenViewFactory<ScreenT>() as ScreenViewFactory<ScreenT>

0 commit comments

Comments
 (0)