Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ import com.squareup.sample.container.overviewdetail.OverviewDetailConfig.Overvie
import com.squareup.sample.container.overviewdetail.OverviewDetailConfig.Single
import com.squareup.workflow1.ui.WorkflowUiExperimentalApi
import com.squareup.workflow1.ui.LayoutRunner
import com.squareup.workflow1.ui.LayoutRunnerViewFactory
import com.squareup.workflow1.ui.ViewFactory
import com.squareup.workflow1.ui.ViewEnvironment
import com.squareup.workflow1.ui.WorkflowViewStub
Expand Down Expand Up @@ -98,9 +97,8 @@ class OverviewDetailContainer(view: View) : LayoutRunner<OverviewDetailScreen> {
stub.update(combined, viewEnvironment + (OverviewDetailConfig to Single))
}

companion object : ViewFactory<OverviewDetailScreen> by LayoutRunnerViewFactory(
type = OverviewDetailScreen::class,
companion object : ViewFactory<OverviewDetailScreen> by LayoutRunner.bind(
layoutId = R.layout.overview_detail,
runnerConstructor = ::OverviewDetailContainer
constructor = ::OverviewDetailContainer
)
}
5 changes: 0 additions & 5 deletions workflow-ui/core-android/api/core-android.api
Original file line number Diff line number Diff line change
Expand Up @@ -105,18 +105,13 @@ public abstract interface class com/squareup/workflow1/ui/ViewRegistry {
public static final field Companion Lcom/squareup/workflow1/ui/ViewRegistry$Companion;
public abstract fun getFactoryFor (Lkotlin/reflect/KClass;)Lcom/squareup/workflow1/ui/ViewFactory;
public abstract fun getKeys ()Ljava/util/Set;
public abstract fun hasViewBeenBound (Landroid/view/View;)Z
}

public final class com/squareup/workflow1/ui/ViewRegistry$Companion : com/squareup/workflow1/ui/ViewEnvironmentKey {
public fun getDefault ()Lcom/squareup/workflow1/ui/ViewRegistry;
public synthetic fun getDefault ()Ljava/lang/Object;
}

public final class com/squareup/workflow1/ui/ViewRegistry$DefaultImpls {
public static fun hasViewBeenBound (Lcom/squareup/workflow1/ui/ViewRegistry;Landroid/view/View;)Z
}

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;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,18 +1,3 @@
/*
* Copyright 2019 Square Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.squareup.workflow1.ui

import android.content.Context
Expand All @@ -32,54 +17,8 @@ typealias ViewBindingInflater<BindingT> = (LayoutInflater, ViewGroup?, Boolean)
* by a [ViewRegistry]. (Use [BuilderViewFactory] if you want to build views from code rather
* than layouts.)
*
* Typical usage is to have a [LayoutRunner]'s `companion object` implement
* [ViewFactory] by delegating to [LayoutRunner.bind], specifying the layout resource
* it expects to drive.
*
* class HelloLayoutRunner(view: View) : LayoutRunner<Rendering> {
* private val messageView: TextView = view.findViewById(R.id.hello_message)
*
* override fun showRendering(rendering: Rendering) {
* messageView.text = rendering.message
* messageView.setOnClickListener { rendering.onClick(Unit) }
* }
*
* companion object : ViewFactory<Rendering> by bind(
* R.layout.hello_goodbye_layout, ::HelloLayoutRunner
* )
* }
*
* This pattern allows us to assemble a [ViewRegistry] out of the [LayoutRunner] classes
* themselves.
*
* val TicTacToeViewBuilders = ViewRegistry(
* NewGameLayoutRunner, GamePlayLayoutRunner, GameOverLayoutRunner
* )
*
* ## AndroidX ViewBinding
*
* [AndroidX ViewBinding][ViewBinding] is supported in two ways.
* In most cases, you can use the `bind` function that takes a function and avoid implementing
* [LayoutRunner] at all.
*
* If you need to perform some set up before [showRendering] is called, use the
* `bind` overload that takes:
* - a reference to a `ViewBinding.inflate` method and
* - a [LayoutRunner] constructor that accepts a [ViewBinding]
*
* class HelloLayoutRunner(private val binding: HelloGoodbyeLayoutBinding) : LayoutRunner<Rendering> {
*
* override fun showRendering(rendering: Rendering) {
* binding.messageView.text = rendering.message
* binding.messageView.setOnClickListener { rendering.onClick(Unit) }
* }
*
* companion object : ViewFactory<Rendering> by bind(
* HelloGoodbyeLayoutBinding::inflate, ::HelloLayoutRunner
* )
* }
*
* If the view does not need to be initialized, the [bind] function can be used instead.
* If you're using [AndroidX ViewBinding][ViewBinding] you likely won't need to
* implement this interface at all. For details, see the three overloads of [LayoutRunner.bind].
*/
@WorkflowUiExperimentalApi
interface LayoutRunner<RenderingT : Any> {
Expand All @@ -90,28 +29,18 @@ interface LayoutRunner<RenderingT : Any> {

companion object {
/**
* Creates a [ViewFactory] that inflates [layoutId] to show renderings of type [RenderingT],
* using a [LayoutRunner] created by [constructor].
*/
inline fun <reified RenderingT : Any> bind(
@LayoutRes layoutId: Int,
noinline constructor: (View) -> LayoutRunner<RenderingT>
): ViewFactory<RenderingT> = LayoutRunnerViewFactory(RenderingT::class, layoutId, constructor)

/**
* Creates a [ViewFactory] that [inflates][bindingInflater] a [ViewBinding] ([BindingT]) to show
* renderings of type [RenderingT], using [showRendering].
* Creates a [ViewFactory] that [inflates][bindingInflater] a [ViewBinding] ([BindingT])
* to show renderings of type [RenderingT], using [a lambda][showRendering].
*
* ```
* val HelloBinding: ViewFactory<Rendering> =
* bindViewBinding(HelloGoodbyeLayoutBinding::inflate) { rendering, viewEnvironment ->
* helloMessage.text = rendering.message
* helloMessage.setOnClickListener { rendering.onClick(Unit) }
* }
* ```
* val HelloBinding: ViewFactory<Rendering> =
* LayoutRunner.bind(HelloGoodbyeLayoutBinding::inflate) { rendering, viewEnvironment ->
* helloMessage.text = rendering.message
* helloMessage.setOnClickListener { rendering.onClick(Unit) }
* }
*
* If you need to initialize your view before [showRendering] is called, create a [LayoutRunner]
* and create a binding using `LayoutRunner.bind` instead.
* If you need to initialize your view before [showRendering] is called,
* implement [LayoutRunner] and create a binding using the `bind` variant
* that accepts a `(ViewBinding) -> LayoutRunner` function, below.
*/
inline fun <BindingT : ViewBinding, reified RenderingT : Any> bind(
noinline bindingInflater: ViewBindingInflater<BindingT>,
Expand All @@ -126,22 +55,46 @@ interface LayoutRunner<RenderingT : Any> {
}

/**
* Creates a [ViewFactory] that [inflates][bindingInflater] a [BindingT] to show renderings of
* type [RenderingT], using a [LayoutRunner] created by [constructor].
* Creates a [ViewFactory] that [inflates][bindingInflater] a [ViewBinding] ([BindingT])
* to show renderings of type [RenderingT], using a [LayoutRunner] created by [constructor].
* Handy if you need to perform some set up before [showRendering] is called.
*
* class HelloLayoutRunner(
* private val binding: HelloGoodbyeLayoutBinding
* ) : LayoutRunner<Rendering> {
*
* override fun showRendering(rendering: Rendering) {
* binding.messageView.text = rendering.message
* binding.messageView.setOnClickListener { rendering.onClick(Unit) }
* }
*
* companion object : ViewFactory<Rendering> by bind(
* HelloGoodbyeLayoutBinding::inflate, ::HelloLayoutRunner
* )
* }
*
* If the view doesn't need to be initialized before [showRendering] is called,
* [bind] can be used instead, which just takes a lambda instead requiring a whole
* [LayoutRunner] class.
* use the variant above which just takes a lambda.
*/
inline fun <BindingT : ViewBinding, reified RenderingT : Any> bind(
noinline bindingInflater: ViewBindingInflater<BindingT>,
noinline constructor: (BindingT) -> LayoutRunner<RenderingT>
): ViewFactory<RenderingT> =
ViewBindingViewFactory(RenderingT::class, bindingInflater, constructor)

/**
* Creates a [ViewFactory] that inflates [layoutId] to show renderings of type [RenderingT],
* using a [LayoutRunner] created by [constructor]. Avoids any use of
* [AndroidX ViewBinding][ViewBinding].
*/
inline fun <reified RenderingT : Any> bind(
@LayoutRes layoutId: Int,
noinline constructor: (View) -> LayoutRunner<RenderingT>
): ViewFactory<RenderingT> = LayoutRunnerViewFactory(RenderingT::class, layoutId, constructor)

/**
* Creates a [ViewFactory] that inflates [layoutId] to "show" renderings of type [RenderingT],
* with a no-op [LayoutRunner]. Handy for showing static views.
* with a no-op [LayoutRunner]. Handy for showing static views, e.g. when prototyping.
*/
inline fun <reified RenderingT : Any> bindNoRunner(
@LayoutRes layoutId: Int
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ import kotlin.reflect.KClass
* details.
*/
@WorkflowUiExperimentalApi
class LayoutRunnerViewFactory<RenderingT : Any>(
@PublishedApi
internal class LayoutRunnerViewFactory<RenderingT : Any>(
override val type: KClass<RenderingT>,
@LayoutRes private val layoutId: Int,
private val runnerConstructor: (View) -> LayoutRunner<RenderingT>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,10 @@ internal val defaultViewFactories = ViewRegistry(NamedViewFactory)
*
* Two concrete [ViewFactory] implementations are provided:
*
* - [LayoutRunner.Binding], allowing the easy pairing of Android XML layout resources with
* [LayoutRunner]s to drive them.
* - The various [bind][LayoutRunner.bind] methods on [LayoutRunner] allow easy use of
* Android XML layout resources and [AndroidX ViewBinding][androidx.viewbinding.ViewBinding].
*
* - [BuilderViewFactory], which can build views from code.
* - [BuilderViewFactory] allows views to be built from code.
*
* Registries can be assembled via concatenation, making it easy to snap together screen sets.
* For example:
Expand All @@ -54,8 +54,7 @@ internal val defaultViewFactories = ViewRegistry(NamedViewFactory)
* AuthViewFactories + TicTacToeViewFactories
*
* In the above example, note that the `companion object`s of the various [LayoutRunner] classes
* honor a convention of implementing [ViewFactory], in aid of this kind of assembly. See the
* class doc on [LayoutRunner] for details.
* honor a convention of implementing [ViewFactory], in aid of this kind of assembly.
*/
@WorkflowUiExperimentalApi
interface ViewRegistry {
Expand All @@ -79,14 +78,6 @@ interface ViewRegistry {
renderingType: KClass<out RenderingT>
): ViewFactory<RenderingT>

/**
* This method is not for general use, it's called by [buildView] to validate views returned by
* [ViewFactory]s.
*
* Returns true iff [view] has been bound to a [ShowRenderingTag] by calling [bindShowRendering].
*/
fun hasViewBeenBound(view: View): Boolean = view.getRendering<Any>() != null

companion object : ViewEnvironmentKey<ViewRegistry>(ViewRegistry::class) {
override val default: ViewRegistry
get() = error("There should always be a ViewRegistry hint, this is bug in Workflow.")
Expand Down Expand Up @@ -118,8 +109,8 @@ fun ViewRegistry(): ViewRegistry = TypedViewRegistry()
*
* @throws IllegalArgumentException if no factory can be find for type [RenderingT]
*
* @throws IllegalStateException if [ViewRegistry.hasViewBeenBound] returns false (i.e. if the
* matching [ViewFactory] fails to call [View.bindShowRendering] when constructing the view)
* @throws IllegalStateException if the matching [ViewFactory] fails to call
* [View.bindShowRendering] when constructing the view
*/
@WorkflowUiExperimentalApi
fun <RenderingT : Any> ViewRegistry.buildView(
Expand All @@ -136,7 +127,7 @@ fun <RenderingT : Any> ViewRegistry.buildView(
container
)
.apply {
check(hasViewBeenBound(this)) {
check(this.getRendering<Any>() != null) {
"View.bindShowRendering should have been called for $this, typically by the " +
"${ViewFactory::class.java.name} that created it."
}
Expand Down

This file was deleted.