-
Notifications
You must be signed in to change notification settings - Fork 112
Introduces ScreenViewFactory.forWrapper
#805
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
This file was deleted.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -13,25 +13,6 @@ import com.squareup.workflow1.ui.ScreenViewFactory.Companion.fromViewBinding | |
| @WorkflowUiExperimentalApi | ||
| public typealias ViewBindingInflater<BindingT> = (LayoutInflater, ViewGroup?, Boolean) -> BindingT | ||
|
|
||
| /** | ||
| * The function that updates a [View] instance built by a [ScreenViewFactory]. | ||
| * Each [ScreenViewRunner] instance is paired with the single [View] instance, | ||
| * its neighbor in a [ScreenViewHolder]. | ||
| * | ||
| * This is the interface you'll implement directly to update Android view code | ||
| * from your [Screen] renderings. A [ScreenViewRunner] serves as the strategy | ||
| * object of a [ScreenViewHolder] instantiated by a [ScreenViewFactory] -- the | ||
| * runner provides the implementation for the holder's [ScreenViewHolder.show] | ||
| * method. | ||
| */ | ||
| @WorkflowUiExperimentalApi | ||
| public fun interface ScreenViewRunner<in ScreenT : Screen> { | ||
| public fun showRendering( | ||
| rendering: ScreenT, | ||
| viewEnvironment: ViewEnvironment | ||
| ) | ||
| } | ||
|
|
||
| /** | ||
| * A [ViewRegistry.Entry] that can build Android [View] instances, along with functions | ||
| * 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 | |
| buildView(initialRendering, initialEnvironment, context, container) | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Creates a [ScreenViewFactory] for [WrapperT] that finds and delegates to | ||
| * the one for [WrappedT]. Allows [WrapperT] to wrap instances of [WrappedT] | ||
| * to add information or behavior, without requiring wasteful wrapping in the view system. | ||
| * | ||
| * One general note: when creating a wrapper rendering, you're very likely to want it | ||
| * to implement [Compatible], to ensure that checks made to update or replace a view | ||
| * are based on the wrapped item. Each wrapper example below illustrates this. | ||
| * | ||
| * This a simpler variant of the like named function that takes three arguments, for | ||
| * use when there is no need to manipulate the [ScreenViewHolder]. | ||
| * | ||
| * ## Examples | ||
| * | ||
| * To make one rendering type an "alias" for another -- that is, to use the | ||
| * same [ScreenViewFactory] to display it: | ||
| * | ||
| * class RealScreen(val data: String): AndroidScreen<RealScreen> { | ||
| * override val viewFactory = fromLayout<RealScreen>(...) | ||
| * } | ||
| * | ||
| * class AliasScreen(val similarData: String) : AndroidScreen<AliasScreen> { | ||
| * override val viewFactory = forWrapper<AliasScreen, RealScreen> { aliasScreen -> | ||
| * RealScreen(aliasScreen.similarData) | ||
| * } | ||
| * } | ||
| * | ||
| * To make one [Screen] type a wrapper for others: | ||
| * | ||
| * class Wrapper<W>(val wrapped: W: Screen) : AndroidScreen<Wrapper<W>>, Compatible { | ||
| * override val compatibilityKey = Compatible.keyFor(wrapped) | ||
| * override val viewFactory = ScreenViewFactory.forWrapper<Wrapper<W>, W> { it.wrapped } | ||
| * } | ||
| * | ||
| * To make a wrapper that adds information to the [ViewEnvironment]: | ||
| * | ||
| * class ReverseNeutronFlowPolarity : ViewEnvironmentKey<Boolean>(Boolean::class) { | ||
| * override val default = false | ||
| * } | ||
| * | ||
| * class ReversePolarityScreen<W : Screen>( | ||
| * val wrapped: W | ||
| * ) : AndroidScreen<ReversePolarityScreen<W>>, Compatible { | ||
| * override val compatibilityKey: String = Compatible.keyFor(wrapped) | ||
| * override val viewFactory = forWrapper<OverrideNeutronFlow<W>, Screen> { | ||
| * it.wrapped.withEnvironment( | ||
| * Environment.EMPTY + (ReverseNeutronFlowPolarity to true) | ||
| * ) | ||
| * } | ||
| * } | ||
| * | ||
| * @param unwrap a function to extract [WrappedT] instances from [WrapperT]s. | ||
| */ | ||
| @WorkflowUiExperimentalApi | ||
| public inline fun < | ||
| reified WrapperT : Screen, | ||
| WrappedT : Screen | ||
| > forWrapper( | ||
| crossinline unwrap: (wrapperScreen: WrapperT) -> WrappedT, | ||
| ): ScreenViewFactory<WrapperT> = forWrapper( | ||
| unwrap = unwrap, | ||
| beforeShowing = {} | ||
| ) { _, wrapper, e, showWrapper -> | ||
| showWrapper(unwrap(wrapper), e) | ||
| } | ||
|
|
||
| /** | ||
| * Creates a [ScreenViewFactory] for [WrapperT] that finds and delegates to | ||
| * the one for [WrappedT]. Allows [WrapperT] to wrap instances of [WrappedT] | ||
| * to add information or behavior, without requiring wasteful wrapping in the view system. | ||
| * | ||
| * This fully featured variant of the function is able to initialize the freshly | ||
| * created [ScreenViewHolder], and transform the wrapped [ScreenViewHolder.runner]. | ||
| * | ||
| * To make a wrapper that customizes [View] initialization: | ||
| * | ||
| * class WithTutorialTips<W : Screen>( | ||
| * val wrapped: W | ||
| * ) : AndroidScreen<WithTutorialTips<W>>, Compatible { | ||
| * override val compatibilityKey = Compatible.keyFor(wrapped) | ||
| * override val viewFactory = forWrapper<WithTutorialTips<W>, W>( | ||
| * unwrap = { it.wrapped }, | ||
| * beforeShowing = { TutorialTipRunner.initialize(it.view) }, | ||
| * showWrapperScreen = { _, wrapper, environment, showWrapper -> | ||
| * showWrapper(unwrap(wrapper), environment) | ||
| * } | ||
| * ) | ||
| * } | ||
| * | ||
| * @param unwrap a function to extract [WrappedT] instances from [WrapperT]s. | ||
| * | ||
| * @param beforeShowing a function to be invoked immediately after a new [View] is built. | ||
| * | ||
| * @param showWrapperScreen a function to be invoked when an instance of [WrapperT] needs | ||
| * to be shown in a [View] built to display instances of [WrappedT]. Allows | ||
| * pre- and post-processing of the [View]. | ||
| */ | ||
| @WorkflowUiExperimentalApi | ||
| public inline fun < | ||
| reified WrapperT : Screen, | ||
| WrappedT : Screen | ||
| > forWrapper( | ||
|
Comment on lines
+248
to
+251
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This formatting looks odd to me? Can we move the generics up on the same line and the function name too? |
||
| crossinline unwrap: (wrapperScreen: WrapperT) -> WrappedT, | ||
| crossinline beforeShowing: (viewHolder: ScreenViewHolder<WrapperT>) -> Unit = {}, | ||
| crossinline showWrapperScreen: ( | ||
| view: View, | ||
| wrapperScreen: WrapperT, | ||
| environment: ViewEnvironment, | ||
| showUnwrappedScreen: (WrappedT, ViewEnvironment) -> Unit | ||
| ) -> Unit, | ||
| ): ScreenViewFactory<WrapperT> = | ||
| fromCode { initialRendering, initialEnvironment, context, container -> | ||
| val wrappedFactory = unwrap(initialRendering).toViewFactory(initialEnvironment) | ||
| val wrapperFactory = wrappedFactory.toUnwrappingViewFactory(unwrap, showWrapperScreen) | ||
| wrapperFactory.buildView( | ||
| initialRendering, initialEnvironment, context, container | ||
| ).also { beforeShowing(it) } | ||
| } | ||
| } | ||
| } | ||
|
|
||
|
|
@@ -266,140 +366,18 @@ public fun <ScreenT : Screen> ScreenViewFactory<ScreenT>.startShowing( | |
| */ | ||
| @WorkflowUiExperimentalApi | ||
| public fun interface ViewStarter { | ||
| /** Called from [View.start]. [doStart] must be invoked. */ | ||
| /** Called from [ScreenViewFactory.startShowing]. [doStart] must be invoked. */ | ||
| public fun startView( | ||
| view: View, | ||
| doStart: () -> Unit | ||
| ) | ||
| } | ||
|
|
||
| /** | ||
| * Transforms a [ScreenViewFactory] of [WrappedT] into one that can handle | ||
| * instances of [WrapperT]. Allows [WrapperT] to wrap instances of [WrappedT] | ||
| * to add information or behavior, without requiring wasteful wrapping in the view system. | ||
| * | ||
| * One general note: when creating a wrapper rendering, you're very likely to want it | ||
| * to implement [Compatible], to ensure that checks made to update or replace a view | ||
| * are based on the wrapped item. Each wrapper example below illustrates this. | ||
| * | ||
| * This a simpler variant of the like named function that takes two arguments, for | ||
| * use when there is no need to customize the [view][ScreenViewHolder.view] or | ||
| * the [environment][ScreenViewHolder.environment]. | ||
| * | ||
| * ## Examples | ||
| * | ||
| * To make one rendering type an "alias" for another -- that is, to use the | ||
| * same [ScreenViewFactory] to display it: | ||
| * | ||
| * class RealScreen(val data: String): Screen | ||
| * object RealScreenViewFactory = ScreenViewFactory.fromLayout(...) | ||
| * | ||
| * class AliasScreen(val similarData: String) : Screen | ||
| * | ||
| * object AliasScreenViewFactory = | ||
| * RealScreenViewFactory.toUnwrappingViewFactory<AliasScreen, RealScreen> { aliasScreen -> | ||
| * RealScreen(aliasScreen.similarData) | ||
| * } | ||
| * | ||
| * To make one rendering type a wrapper for others: | ||
| * | ||
| * class Wrapper<W>(val wrapped: W: Screen) : Screen, Compatible { | ||
| * override val compatibilityKey = Compatible.keyFor(wrapped) | ||
| * } | ||
| * | ||
| * fun <W : Screen> WrapperViewFactory() = | ||
| * ScreenViewFactory.forBuiltView<Wrapper<W>> { wrapper, env, context, container -> | ||
| * // Get the view factory of the wrapped screen. | ||
| * wrapper.wrapped.toViewFactory(env) | ||
| * // Transform it to factory that accepts Wrapper<W> | ||
| * .toUnwrappingViewFactory<Wrapper<W>, W> { it.wrapped } | ||
| * // Delegate to the transformed factory to build the view. | ||
| * .buildView(wrapper, env, context, container) | ||
| * } | ||
| * | ||
| * To make a wrapper that adds information to the [ViewEnvironment]: | ||
| * | ||
| * class NeutronFlowPolarity(val reversed: Boolean) { | ||
| * companion object : ViewEnvironmentKey<NeutronFlowPolarity>( | ||
| * NeutronFlowPolarity::class | ||
| * ) { | ||
| * override val default: NeutronFlowPolarity = | ||
| * NeutronFlowPolarity(reversed = false) | ||
| * } | ||
| * } | ||
| * | ||
| * class OverrideNeutronFlow<W : Screen>( | ||
| * val wrapped: W, | ||
| * val polarity: NeutronFlowPolarity | ||
| * ) : Screen, Compatible { | ||
| * override val compatibilityKey: String = Compatible.keyFor(wrapped) | ||
| * } | ||
| * | ||
| * fun <W : Screen> OverrideNeutronFlowViewFactory() = | ||
| * ScreenViewFactory.forBuiltView<OverrideNeutronFlow<W>> { wrapper, env, context, container -> | ||
| * // Get the view factory of the wrapped screen. | ||
| * wrapper.wrapped.toViewFactory(env) | ||
| * // Transform it to factory that accepts OverrideNeutronFlow<W>, by | ||
| * // replacing the OverrideNeutronFlow<W> with an EnvironmentScreen<W> | ||
| * .toUnwrappingViewFactory<OverrideNeutronFlow<W>, EnvironmentScreen<W>> { | ||
| * it.wrapped.withEnvironment( | ||
| * Environment.EMPTY + (NeutronFlowPolarity to it.polarity) | ||
| * ) | ||
| * } | ||
| * // Delegate to the transformed factory to build the view. | ||
| * .buildView(wrapper, env, context, container) | ||
| * } | ||
| * | ||
| * @param unwrap a function to extract [WrappedT] instances from [WrapperT]s. | ||
| */ | ||
| @WorkflowUiExperimentalApi | ||
| public inline fun < | ||
| reified WrapperT : Screen, | ||
| WrappedT : Screen | ||
| > ScreenViewFactory<WrappedT>.toUnwrappingViewFactory( | ||
| crossinline unwrap: (wrapperScreen: WrapperT) -> WrappedT, | ||
| ): ScreenViewFactory<WrapperT> = | ||
| toUnwrappingViewFactory(unwrap) { _, ws, e, su -> su(unwrap(ws), e) } | ||
|
Comment on lines
-356
to
-362
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I already regret deleting this, probably going to bring it back. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nope, still no use case. |
||
|
|
||
| /** | ||
| * Transforms a [ScreenViewFactory] of [WrappedT] into one that can handle | ||
| * instances of [WrapperT]. | ||
| * | ||
| * One general note: when creating a wrapper rendering, you're very likely to want it | ||
| * to implement [Compatible], to ensure that checks made to update or replace a view | ||
| * are based on the wrapped item. Each wrapper example below illustrates this. | ||
| * | ||
| * Also see the simpler variant of this function that takes only an [unwrap] argument. | ||
| * | ||
| * ## Example | ||
| * | ||
| * To make a wrapper that customizes [View] initialization: | ||
| * | ||
| * class WithTutorialTips<W : Screen>(val wrapped: W) : Screen, Compatible { | ||
| * override val compatibilityKey = Compatible.keyFor(wrapped) | ||
| * } | ||
| * | ||
| * fun <W: Screen> WithTutorialTipsFactory<W>() = | ||
| * ScreenViewFactory.forBuiltView<WithTutorialTips<*>> = { | ||
| * initialRendering, initialEnv, context, container -> | ||
| * // Get the view factory of the wrapped screen. | ||
| * initialRendering.wrapped.toViewFactory(initialEnv) | ||
| * // Transform it to factory that accepts WithTutorialTips<W> | ||
| * .toUnwrappingViewFactory<WithTutorialTips<W>, W>( | ||
| * unwrap = { it.wrapped }, | ||
| * showWrapperScreen = { view, withTips, env, showUnwrapped -> | ||
| * TutorialTipRunner.run(view) | ||
| * showUnwrapped(withTips.wrapped, env) | ||
| * } | ||
| * // Delegate to the transformed factory to build the view. | ||
| * .buildView(initialRendering, initialEnv, context, container) | ||
| * } | ||
| * | ||
| * @param unwrap a function to extract [WrappedT] instances from [WrapperT]s. | ||
| * | ||
| * @param showWrapperScreen a function invoked when an instance of [WrapperT] needs | ||
| * to be shown in a [View] built to display instances of [WrappedT]. Allows | ||
| * pre- and post-processing of the [View]. | ||
| * It is usually more convenient to call [ScreenViewFactory.forWrapper]. | ||
| */ | ||
| @WorkflowUiExperimentalApi | ||
| public inline fun < | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Awesome examples! Thank-you!