Skip to content

Commit 727ab92

Browse files
committed
Makes ViewRegistry extra for experts instead of torture for the masses.
Introduces `ViewRendering` and `AndroidContainerView`. * `ViewRendering` is a marker interface in the UI common core, identifying UI views. * `AndroidContainerView` is able to tie particular `ViewRenderings` to particular `ViewFactories`, and is now built into `ViewRegistry` automatically -- just like `NamedViewFactory`. To make this work, we teach `ViewRegistry` a new trick: if it can't find a registered `ViewFactory` for a rendering, it checks to see if the rendering itself implements `ViewFactory`, and if so calls it directly. Given all this, it's no longer crucial for developers to define and provide a `ViewRegistry`, so we get rid of all the nannying that forced them to provide one.
1 parent 679256c commit 727ab92

File tree

13 files changed

+97
-179
lines changed

13 files changed

+97
-179
lines changed
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package com.squareup.sample.helloworkflow
2+
3+
import com.squareup.sample.helloworkflow.HelloWorkflow.Rendering
4+
import com.squareup.sample.helloworkflow.databinding.HelloGoodbyeLayoutBinding
5+
import com.squareup.workflow1.ui.AndroidViewContainer
6+
import com.squareup.workflow1.ui.LayoutRunner
7+
import com.squareup.workflow1.ui.WorkflowUiExperimentalApi
8+
9+
@Suppress("FunctionName")
10+
@OptIn(WorkflowUiExperimentalApi::class)
11+
fun HelloView(rendering: Rendering) = AndroidViewContainer(
12+
rendering,
13+
LayoutRunner.bind(HelloGoodbyeLayoutBinding::inflate) { r, _ ->
14+
helloMessage.text = r.message
15+
helloMessage.setOnClickListener { r.onClick() }
16+
})

samples/hello-workflow/src/main/java/com/squareup/sample/helloworkflow/HelloViewFactory.kt

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

samples/hello-workflow/src/main/java/com/squareup/sample/helloworkflow/HelloWorkflow.kt

Lines changed: 11 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,54 +1,44 @@
1-
/*
2-
* Copyright 2019 Square Inc.
3-
*
4-
* Licensed under the Apache License, Version 2.0 (the "License");
5-
* you may not use this file except in compliance with the License.
6-
* You may obtain a copy of the License at
7-
*
8-
* http://www.apache.org/licenses/LICENSE-2.0
9-
*
10-
* Unless required by applicable law or agreed to in writing, software
11-
* distributed under the License is distributed on an "AS IS" BASIS,
12-
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13-
* See the License for the specific language governing permissions and
14-
* limitations under the License.
15-
*/
161
package com.squareup.sample.helloworkflow
172

18-
import com.squareup.sample.helloworkflow.HelloWorkflow.Rendering
193
import com.squareup.sample.helloworkflow.HelloWorkflow.State
204
import com.squareup.sample.helloworkflow.HelloWorkflow.State.Goodbye
215
import com.squareup.sample.helloworkflow.HelloWorkflow.State.Hello
226
import com.squareup.workflow1.Snapshot
237
import com.squareup.workflow1.StatefulWorkflow
248
import com.squareup.workflow1.action
259
import com.squareup.workflow1.parse
10+
import com.squareup.workflow1.ui.AndroidViewContainer
11+
import com.squareup.workflow1.ui.ViewRendering
12+
import com.squareup.workflow1.ui.WorkflowUiExperimentalApi
2613

27-
object HelloWorkflow : StatefulWorkflow<Unit, State, Nothing, Rendering>() {
14+
object HelloWorkflow : StatefulWorkflow<Unit, State, Nothing, AndroidViewContainer<*>>() {
2815
enum class State {
2916
Hello,
3017
Goodbye
3118
}
3219

20+
@OptIn(WorkflowUiExperimentalApi::class)
3321
data class Rendering(
3422
val message: String,
3523
val onClick: () -> Unit
36-
)
24+
) : ViewRendering
3725

3826
override fun initialState(
3927
props: Unit,
4028
snapshot: Snapshot?
4129
): State = snapshot?.bytes?.parse { source -> if (source.readInt() == 1) Hello else Goodbye }
42-
?: Hello
30+
?: Hello
4331

4432
override fun render(
4533
props: Unit,
4634
state: State,
4735
context: RenderContext
48-
): Rendering {
49-
return Rendering(
36+
): AndroidViewContainer<*> {
37+
return HelloView(
38+
Rendering(
5039
message = state.name,
5140
onClick = { context.actionSink.send(helloAction) }
41+
)
5242
)
5343
}
5444

samples/hello-workflow/src/main/java/com/squareup/sample/helloworkflow/HelloWorkflowActivity.kt

Lines changed: 3 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,20 @@
1-
/*
2-
* Copyright 2019 Square Inc.
3-
*
4-
* Licensed under the Apache License, Version 2.0 (the "License");
5-
* you may not use this file except in compliance with the License.
6-
* You may obtain a copy of the License at
7-
*
8-
* http://www.apache.org/licenses/LICENSE-2.0
9-
*
10-
* Unless required by applicable law or agreed to in writing, software
11-
* distributed under the License is distributed on an "AS IS" BASIS,
12-
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13-
* See the License for the specific language governing permissions and
14-
* limitations under the License.
15-
*/
161
package com.squareup.sample.helloworkflow
172

183
import android.os.Bundle
194
import androidx.appcompat.app.AppCompatActivity
205
import com.squareup.workflow1.SimpleLoggingWorkflowInterceptor
6+
import com.squareup.workflow1.mapRendering
7+
import com.squareup.workflow1.ui.AndroidViewContainer
218
import com.squareup.workflow1.ui.WorkflowUiExperimentalApi
229
import com.squareup.workflow1.ui.ViewRegistry
2310
import com.squareup.workflow1.ui.WorkflowRunner
2411
import com.squareup.workflow1.ui.setContentWorkflow
2512

26-
@OptIn(WorkflowUiExperimentalApi::class)
27-
private val viewRegistry = ViewRegistry(HelloViewFactory)
28-
2913
@OptIn(WorkflowUiExperimentalApi::class)
3014
class HelloWorkflowActivity : AppCompatActivity() {
3115
override fun onCreate(savedInstanceState: Bundle?) {
3216
super.onCreate(savedInstanceState)
33-
setContentWorkflow(viewRegistry) {
17+
setContentWorkflow {
3418
WorkflowRunner.Config(
3519
HelloWorkflow,
3620
interceptors = listOf(SimpleLoggingWorkflowInterceptor())
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package com.squareup.workflow1.ui
2+
3+
import android.content.Context
4+
import android.view.View
5+
import android.view.ViewGroup
6+
7+
@OptIn(WorkflowUiExperimentalApi::class)
8+
class AndroidViewContainer<V : ViewRendering>(
9+
val rendering: V,
10+
val viewFactory: ViewFactory<V>
11+
) : ViewFactory<AndroidViewContainer<V>> {
12+
override val type = AndroidViewContainer::class
13+
14+
override fun buildView(
15+
initialRendering: AndroidViewContainer<V>,
16+
initialViewEnvironment: ViewEnvironment,
17+
contextForNewView: Context,
18+
container: ViewGroup?
19+
): View {
20+
return viewFactory.buildView(
21+
initialRendering.rendering, initialViewEnvironment, contextForNewView, container
22+
).also { view ->
23+
val innerShowRendering = view.getShowRendering<V>()!!
24+
view.bindShowRendering(initialRendering, initialViewEnvironment) { androidRendering, env ->
25+
innerShowRendering(androidRendering.rendering, env)
26+
}
27+
}
28+
}
29+
}

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

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -42,14 +42,7 @@ internal class CompositeViewRegistry private constructor(
4242

4343
override fun <RenderingT : Any> getFactoryFor(
4444
renderingType: KClass<out RenderingT>
45-
): ViewFactory<RenderingT> = getRegistryFor(renderingType).getFactoryFor(renderingType)
46-
47-
private fun getRegistryFor(renderingType: KClass<out Any>): ViewRegistry {
48-
return requireNotNull(registriesByKey[renderingType]) {
49-
"A ${ViewFactory::class.java.name} should have been registered " +
50-
"to display a $renderingType."
51-
}
52-
}
45+
): ViewFactory<RenderingT>? = registriesByKey[renderingType]?.getFactoryFor(renderingType)
5346

5447
companion object {
5548
private fun mergeRegistries(vararg registries: ViewRegistry): Map<KClass<*>, ViewRegistry> {

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

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,3 @@
1-
/*
2-
* Copyright 2019 Square Inc.
3-
*
4-
* Licensed under the Apache License, Version 2.0 (the "License");
5-
* you may not use this file except in compliance with the License.
6-
* You may obtain a copy of the License at
7-
*
8-
* http://www.apache.org/licenses/LICENSE-2.0
9-
*
10-
* Unless required by applicable law or agreed to in writing, software
11-
* distributed under the License is distributed on an "AS IS" BASIS,
12-
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13-
* See the License for the specific language governing permissions and
14-
* limitations under the License.
15-
*/
161
package com.squareup.workflow1.ui
172

183
/**

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

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -40,11 +40,8 @@ internal class TypedViewRegistry private constructor(
4040

4141
override fun <RenderingT : Any> getFactoryFor(
4242
renderingType: KClass<out RenderingT>
43-
): ViewFactory<RenderingT> {
43+
): ViewFactory<RenderingT>? {
4444
@Suppress("UNCHECKED_CAST")
45-
return requireNotNull(bindings[renderingType] as? ViewFactory<RenderingT>) {
46-
"A ${ViewFactory::class.java.name} should have been registered " +
47-
"to display a $renderingType."
48-
}
45+
return bindings[renderingType] as? ViewFactory<RenderingT>
4946
}
5047
}

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

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -30,17 +30,11 @@ typealias ContainerHints = ViewEnvironment
3030
* its children via [View.showRendering][android.view.View.showRendering] et al.
3131
* Allows container views to give descendants information about the context in which
3232
* they're drawing.
33-
*
34-
* Every [ViewEnvironment] includes a [ViewRegistry]. This allows container views to
35-
* make recursive [ViewRegistry.buildView] calls to build child views to show nested renderings.
3633
*/
3734
@WorkflowUiExperimentalApi
38-
class ViewEnvironment private constructor(
39-
private val map: Map<ViewEnvironmentKey<*>, Any>
35+
class ViewEnvironment(
36+
val map: Map<ViewEnvironmentKey<*>, Any> = emptyMap()
4037
) {
41-
constructor(registry: ViewRegistry) :
42-
this(mapOf<ViewEnvironmentKey<*>, Any>(ViewRegistry to registry))
43-
4438
@Suppress("UNCHECKED_CAST")
4539
operator fun <T : Any> get(key: ViewEnvironmentKey<T>): T = map[key] as? T ?: key.default
4640

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

Lines changed: 25 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -70,17 +70,15 @@ interface ViewRegistry {
7070
/**
7171
* This method is not for general use, use [WorkflowViewStub] instead.
7272
*
73-
* Returns the [ViewFactory] that was registered for the given [renderingType].
74-
*
75-
* @throws IllegalArgumentException if no factory can be found for type [RenderingT]
73+
* Returns the [ViewFactory] that was registered for the given [renderingType], or null
74+
* if none was found.
7675
*/
7776
fun <RenderingT : Any> getFactoryFor(
7877
renderingType: KClass<out RenderingT>
79-
): ViewFactory<RenderingT>
78+
): ViewFactory<RenderingT>?
8079

8180
companion object : ViewEnvironmentKey<ViewRegistry>(ViewRegistry::class) {
82-
override val default: ViewRegistry
83-
get() = error("There should always be a ViewRegistry hint, this is bug in Workflow.")
81+
override val default: ViewRegistry get() = ViewRegistry()
8482
}
8583
}
8684

@@ -105,7 +103,7 @@ fun ViewRegistry(): ViewRegistry = TypedViewRegistry()
105103
* It is usually more convenient to use [WorkflowViewStub] than to call this method directly.
106104
*
107105
* Creates a [View] to display [initialRendering], which can be updated via calls
108-
* to [View.showRendering].
106+
* to [View.showRendering]. Uses
109107
*
110108
* @throws IllegalArgumentException if no factory can be find for type [RenderingT]
111109
*
@@ -119,19 +117,27 @@ fun <RenderingT : Any> ViewRegistry.buildView(
119117
contextForNewView: Context,
120118
container: ViewGroup? = null
121119
): View {
122-
return getFactoryFor(initialRendering::class)
123-
.buildView(
124-
initialRendering,
125-
initialViewEnvironment,
126-
contextForNewView,
127-
container
128-
)
129-
.apply {
130-
check(this.getRendering<Any>() != null) {
131-
"View.bindShowRendering should have been called for $this, typically by the " +
132-
"${ViewFactory::class.java.name} that created it."
133-
}
120+
@Suppress("UNCHECKED_CAST")
121+
val factory = getFactoryFor(initialRendering::class)
122+
?: (initialRendering as? ViewFactory<RenderingT>)
123+
?: throw IllegalArgumentException(
124+
"A ${ViewFactory::class.qualifiedName} should have been registered " +
125+
"to display ${initialRendering::class.qualifiedName} instances, or that class " +
126+
"should implement ${ViewFactory::class.simpleName}<${initialRendering::class.simpleName}>."
127+
)
128+
129+
return factory.buildView(
130+
initialRendering,
131+
initialViewEnvironment,
132+
contextForNewView,
133+
container
134+
)
135+
.apply {
136+
check(this.getRendering<Any>() != null) {
137+
"View.bindShowRendering should have been called for $this, typically by the " +
138+
"${ViewFactory::class.java.name} that created it."
134139
}
140+
}
135141
}
136142

137143
/**

0 commit comments

Comments
 (0)