Skip to content

Commit 8fa0645

Browse files
Merge pull request #1459 from square/sedwards/update-tutorial
Update tutorial
2 parents 88e7ed0 + 1b38834 commit 8fa0645

38 files changed

+157
-198
lines changed

samples/tutorial/Tutorial1.md

Lines changed: 30 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -23,13 +23,15 @@ We'll start by making pair of `Workflow` and `Screen` classes to back the provid
2323
First let's make `WelcomeWorkflow` from the _Stateful Workflow_ template, adding it to the `tutorial-base` module:
2424

2525
![New Workflow menu item](images/new-workflow.png "Right click tutorial-base/kotlin+java/workflow.tutorial > New > Stateful Workflow")
26-
![New Workflow window](images/workflow-name.png "File name: Welcome Workflow; Props type: Unit; State type: State; Output type: Output: Rendering type: WelcomeScreen")
26+
![New Workflow window](images/workflow-name.png "File name: Welcome Workflow; Props type: Unit; State type: State; Output type: Output: Rendering type: Screen")
2727

2828
The template does not create everything needed.
2929
Manually add placeholder `object`s for `State` and `Output`.
3030

31+
Note that we use the generic `com.squareup.workflow1.ui.Screen` type, for now (see more on that below).
32+
3133
```kotlin
32-
object WelcomeWorkflow : StatefulWorkflow<Unit, State, Output, WelcomeScreen>() {
34+
object WelcomeWorkflow : StatefulWorkflow<Unit, State, Output, Screen>() {
3335

3436
object State
3537
object Output
@@ -42,7 +44,7 @@ object WelcomeWorkflow : StatefulWorkflow<Unit, State, Output, WelcomeScreen>()
4244
override fun render(
4345
renderProps: Unit,
4446
renderState: State,
45-
context: RenderContext
47+
context: RenderContext<Unit, State, Output>
4648
): Screen {
4749
TODO("Render")
4850
}
@@ -78,6 +80,9 @@ private fun welcomeScreenRunner(
7880
}
7981
```
8082

83+
So while our Workflow rendering type is `com.squareup.workflow1.ui.Screen`, we can actually
84+
return a true `WelcomeScreen` (a sub-type) from render().
85+
8186
### `Screen`, `ScreenViewFactory`, and `ScreenViewRunner`
8287

8388
Let's start with what a "screen" is, and how it relates to views.
@@ -172,19 +177,20 @@ The core responsibility of a workflow is to provide a complete rendering / view
172177
every time the related application state updates.
173178

174179
Let's go into the `WelcomeWorkflow` now,
175-
and have it return a `WelcomeScreen` from the `render()` method.
180+
and have it return a `WelcomeScreen` from the `render()` method. Remember `WelcomeScreen` is a
181+
sub-type of `com.squareup.workflow1.ui.Screen`, so we still satisfy the type binding.
176182
177183
While you're here, opt out of persistence by making `snapshotState` return `null`.
178184

179185
```kotlin
180-
object WelcomeWorkflow : StatefulWorkflow<Unit, State, Output, WelcomeScreen>() {
186+
object WelcomeWorkflow : StatefulWorkflow<Unit, State, Output, Screen>() {
181187

182188
//
183189

184190
override fun render(
185191
renderProps: Unit,
186192
renderState: State,
187-
context: RenderContext
193+
context: RenderContext<Unit, State, Output>
188194
): WelcomeScreen = WelcomeScreen(
189195
promptText = "",
190196
onLogInTapped = {}
@@ -206,8 +212,8 @@ object WelcomeWorkflow : StatefulWorkflow<Unit, State, Output, WelcomeScreen>()
206212
207213
### Setting up the Activity
208214
209-
We have our `WelcomeWorkflow` rendering a `WelcomeScreen`
210-
which declares a `ScreenViewFactory` that knows how to display it.
215+
We have our `WelcomeWorkflow` rendering a `Screen` (we know will be `WelcomeScreen`)
216+
and our registry includes a `ScreenViewFactory` that knows how to display a `WelcomeScreen`.
211217
Now let's wire all this up to Android and show it on the screen!
212218

213219
We're about to use functionality related to AndroidX `ViewModel`s,
@@ -235,16 +241,12 @@ class TutorialActivity : AppCompatActivity() {
235241

236242
val model: TutorialViewModel by viewModels()
237243

238-
setContentView(
239-
WorkflowLayout(this).apply {
240-
take(lifecycle, model.renderings)
241-
}
242-
)
244+
workflowContentView.take(lifecycle, model.renderings)
243245
}
244246

245247
class TutorialViewModel(savedState: SavedStateHandle) : ViewModel() {
246248
@OptIn(WorkflowExperimentalRuntime::class)
247-
val renderings: Flow<Screen> by lazy {
249+
val renderings: StateFlow<Screen> by lazy {
248250
renderWorkflowIn(
249251
workflow = WelcomeWorkflow,
250252
scope = viewModelScope,
@@ -257,11 +259,15 @@ class TutorialActivity : AppCompatActivity() {
257259
```
258260

259261
> [!NOTE]
260-
> You'll see that we opt in to some "experimental" `runtimeConfig` options -- all of them, in fact.
261-
> These are optimizations that are in production use at Square.
262-
> They will not be labeled as experimental much longer,
263-
> and will soon be enabled by default.
264-
> In the meantime it is much easier to use them from the start than to turn them on down the road.
262+
> `workflowContentView` is an extension property on `Activity` that creates (or retrieves)
263+
> a `WorkflowLayout` and sets it as the content view automatically.
264+
> `renderWorkflowIn` starts the workflow runtime in the `ViewModel`'s scope and returns
265+
> a `StateFlow` of renderings that `WorkflowLayout.take` collects.
266+
> Note that `WorkflowLayout.take` uses the *Activity* lifecycle (as it's linked to the UI held
267+
> by the Activity). Whereas the runtime uses the *ViewModel*'s lifecycle- so it can survive
268+
> configuration changes, etc.
269+
> We opt into some "experimental" `runtimeConfig` options — all of them, in fact.
270+
> These are optimizations in production use at Square, and will soon be enabled by default.
265271
266272
When the activity is started,
267273
it will start running the `WelcomeWorkflow` and display the Welcome Screen.
@@ -287,7 +293,7 @@ Let's add a `"Hello Workflow!"` message there
287293
to give us some confidence that this thing is working.
288294

289295
```kotlin
290-
object WelcomeWorkflow : StatefulWorkflow<Unit, State, Output, WelcomeScreen>() {
296+
object WelcomeWorkflow : StatefulWorkflow<Unit, State, Output, Screen>() {
291297

292298
data class State(
293299
val prompt: String
@@ -303,7 +309,7 @@ object WelcomeWorkflow : StatefulWorkflow<Unit, State, Output, WelcomeScreen>()
303309
override fun render(
304310
renderProps: Unit,
305311
renderState: State,
306-
context: RenderContext
312+
context: RenderContext<Unit, State, Output>
307313
): WelcomeScreen = WelcomeScreen(
308314
promptText = renderState.prompt,
309315
onLogInTapped = {}
@@ -376,14 +382,14 @@ You could also write:
376382
And let's make an `onLogInTapped` event handler that enqueues one of those `updateName` actions.
377383

378384
```kotlin
379-
object WelcomeWorkflow : StatefulWorkflow<Unit, State, Output, WelcomeScreen>() {
385+
object WelcomeWorkflow : StatefulWorkflow<Unit, State, Output, Screen>() {
380386

381387
//
382388

383389
override fun render(
384390
renderProps: Unit,
385391
renderState: State,
386-
context: RenderContext
392+
context: RenderContext<Unit, State, Output>
387393
): WelcomeScreen = WelcomeScreen(
388394
promptText = renderState.prompt,
389395
onLogInTapped = { name ->
@@ -453,7 +459,7 @@ We'll use `RenderContext.eventHandler` to inline the `updateName` action.
453459
override fun render(
454460
renderProps: Unit,
455461
renderState: State,
456-
context: RenderContext
462+
context: RenderContext<Unit, State, Output>
457463
): WelcomeScreen = WelcomeScreen(
458464
promptText = renderState.prompt,
459465
onLogInTapped = context.eventHandler("onLogInTapped") { name ->

samples/tutorial/Tutorial2.md

Lines changed: 20 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ Modify `State` data class to contain a placeholder parameter, to make the compil
4444
We can leave everything else as the default for now:
4545

4646
```kotlin
47-
object TodoListWorkflow : StatefulWorkflow<Unit, State, Nothing, TodoListScreen>() {
47+
object TodoListWorkflow : StatefulWorkflow<Unit, State, Nothing, Screen>() {
4848

4949
data class State(val placeholder: String = "")
5050

@@ -56,7 +56,7 @@ object TodoListWorkflow : StatefulWorkflow<Unit, State, Nothing, TodoListScreen>
5656
override fun render(
5757
renderProps: Unit,
5858
renderState: State,
59-
context: RenderContext
59+
context: RenderContext<Unit, State, Nothing>
6060
): TodoListScreen {
6161
return TodoListScreen()
6262
}
@@ -70,7 +70,7 @@ object TodoListWorkflow : StatefulWorkflow<Unit, State, Nothing, TodoListScreen>
7070
For now, let's just show this new screen instead of the login screen/workflow. Update the activity to show the `TodoListWorkflow`:
7171

7272
```kotlin
73-
val renderings: Flow<Screen> by lazy {
73+
val renderings: StateFlow<Screen> by lazy {
7474
renderWorkflowIn(
7575
workflow = TodoListWorkflow,
7676
scope = viewModelScope,
@@ -89,7 +89,7 @@ The empty list is rather boring, so let's fill it in with some sample data for n
8989
Update the `State` type to include a list of todo model objects and change `initialState` to include a default one:
9090

9191
```kotlin
92-
object TodoListWorkflow : StatefulWorkflow<Unit, State, Nothing, TodoListScreen>() {
92+
object TodoListWorkflow : StatefulWorkflow<Unit, State, Nothing, Screen>() {
9393

9494
data class TodoModel(
9595
val title: String,
@@ -147,14 +147,14 @@ private fun todoListScreenRunner(
147147
Finally, update `render` for `TodoListWorkflow` to send the titles of the todo models whenever the screen is updated:
148148

149149
```kotlin
150-
object TodoListWorkflow : StatefulWorkflow<Unit, State, Nothing, TodoListScreen>() {
150+
object TodoListWorkflow : StatefulWorkflow<Unit, State, Nothing, Screen>() {
151151

152152
//
153153

154154
override fun render(
155155
renderProps: Unit,
156156
renderState: State,
157-
context: RenderContext
157+
context: RenderContext<Unit, State, Nothing>
158158
): TodoListScreen {
159159
val titles = renderState.todos.map { it.title }
160160

@@ -199,7 +199,7 @@ object RootNavigationWorkflow : StatefulWorkflow<Unit, Unit, Nothing, Screen>()
199199
override fun render(
200200
renderProps: Unit,
201201
renderState: Unit,
202-
context: RenderContext
202+
context: RenderContext<Unit, Unit, Nothing>
203203
): Screen {
204204
// Render a child workflow of type WelcomeWorkflow. When renderChild is called, the
205205
// infrastructure will start a child workflow session if one is not already running.
@@ -223,7 +223,7 @@ But `WelcomeWorkflow`'s output type is currently a simple object: `object Output
223223
For now, delete the `Output` on `WelcomeWorkflow` and replace it with `Nothing`:
224224

225225
```kotlin
226-
object WelcomeWorkflow : StatefulWorkflow<Unit, State, Nothing, WelcomeScreen>() {
226+
object WelcomeWorkflow : StatefulWorkflow<Unit, State, Nothing, Screen>() {
227227
//
228228
}
229229
```
@@ -235,7 +235,7 @@ The override for using `renderChild` on a child Workflow with `Nothing` as its o
235235
override fun render(
236236
renderProps: Unit,
237237
renderState: Unit,
238-
context: RenderContext
238+
context: RenderContext<Unit, Unit, Nothing>
239239
): Screen {
240240
// Render a child workflow of type WelcomeWorkflow. When renderChild is called, the
241241
// infrastructure will start a child workflow session if one is not already running.
@@ -279,7 +279,7 @@ Start by defining the state that needs to be tracked at the root —
279279
specifically which screen we're showing, and the actions to log in and log out:
280280

281281
```kotlin
282-
object RootNavigationWorkflow : StatefulWorkflow<Unit, State, Nothing, WelcomeScreen>() {
282+
object RootNavigationWorkflow : StatefulWorkflow<Unit, State, Nothing, Screen>() {
283283

284284
sealed interface State {
285285
object ShowingWelcome : State
@@ -320,7 +320,7 @@ Change our `OutputT` type from `Output` to a new `data class LoggedIn`
320320
to be able to signal our parent:
321321

322322
```kotlin
323-
object WelcomeWorkflow : StatefulWorkflow<Unit, State, LoggedIn, WelcomeScreen>() {
323+
object WelcomeWorkflow : StatefulWorkflow<Unit, State, LoggedIn, Screen>() {
324324

325325
data class LoggedIn(val username: String)
326326

@@ -350,7 +350,7 @@ Finally, map the output event from `WelcomeWorkflow` in `RootNavigationWorkflow`
350350
override fun render(
351351
renderProps: Unit,
352352
renderState: State,
353-
context: RenderContext
353+
context: RenderContext<Unit, State, Nothing>
354354
): WelcomeScreen {
355355
// Render a child workflow of type WelcomeWorkflow. When renderChild is called, the
356356
// infrastructure will start a child workflow session if one is not already running.
@@ -374,7 +374,7 @@ We'll update the `RootNavigationWorkflow` `render` method to show either the `We
374374
Temporarily define the `OutputT` of `TodoListWorkflow` as `Nothing` (we can only go forward!):
375375

376376
```kotlin
377-
object TodoListWorkflow : StatefulWorkflow<Unit, State, Nothing, TodoListScreen>() {
377+
object TodoListWorkflow : StatefulWorkflow<Unit, State, Nothing, Screen>() {
378378
```
379379

380380
And update the `render` method of `RootNavigationWorkflow`:
@@ -387,7 +387,7 @@ object RootNavigationWorkflow : StatefulWorkflow<Unit, State, Nothing, Screen>()
387387
override fun render(
388388
renderProps: Unit,
389389
renderState: State,
390-
context: RenderContext
390+
context: RenderContext<Unit, State, Nothing>
391391
): Screen {
392392
when (renderState) {
393393
// When the state is ShowingWelcome, delegate to the WelcomeWorkflow.
@@ -468,7 +468,7 @@ and use it to receive a `username` string.
468468
We will also need to update `TodoListScreen` to display it.
469469

470470
```kotlin
471-
object TodoListWorkflow : StatefulWorkflow<ListProps, State, Nothing, TodoListScreen>() {
471+
object TodoListWorkflow : StatefulWorkflow<ListProps, State, Nothing, Screen>() {
472472

473473
data class ListProps(val username: String)
474474

@@ -484,7 +484,7 @@ object TodoListWorkflow : StatefulWorkflow<ListProps, State, Nothing, TodoListSc
484484
override fun render(
485485
renderProps: ListProps,
486486
renderState: State,
487-
context: RenderContext
487+
context: RenderContext<ListProps, State, BackPressed>
488488
): TodoListScreen {
489489
val titles = renderState.todos.map { it.title }
490490

@@ -524,7 +524,7 @@ object RootNavigationWorkflow : StatefulWorkflow<Unit, State, Nothing, Screen>()
524524
override fun render(
525525
renderProps: Unit,
526526
renderState: State,
527-
context: RenderContext
527+
context: RenderContext<Unit, State, Nothing>
528528
): Screen {
529529
when (renderState) {
530530
//
@@ -559,7 +559,7 @@ object RootNavigationWorkflow : StatefulWorkflow<Unit, State, Nothing, BackStack
559559
override fun render(
560560
renderProps: Unit,
561561
renderState: State,
562-
context: RenderContext
562+
context: RenderContext<Unit, State, Nothing>
563563
): BackStackScreen<*> {
564564
// We always render the welcomeScreen regardless of the current state.
565565
// It's either showing or else we may want to pop back to it.
@@ -603,7 +603,7 @@ At the same time, use workflow's handy `View.setBackHandler` function to respond
603603
> and Android's [predictive back gesture](https://developer.android.com/guide/navigation/custom-back/predictive-back-gesture).
604604
605605
```kotlin
606-
object TodoListWorkflow : StatefulWorkflow<ListProps, State, BackPressed, TodoListScreen>() {
606+
object TodoListWorkflow : StatefulWorkflow<ListProps, State, BackPressed, Screen>() {
607607
608608
// ...
609609
@@ -614,7 +614,7 @@ object TodoListWorkflow : StatefulWorkflow<ListProps, State, BackPressed, TodoLi
614614
override fun render(
615615
renderProps: ListProps,
616616
renderState: State,
617-
context: RenderContext
617+
context: RenderContext<ListProps, State, BackPressed>
618618
): TodoListScreen {
619619
val titles = renderState.todos.map { it.title }
620620
return TodoListScreen(

samples/tutorial/Tutorial3.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -170,7 +170,7 @@ object TodoEditWorkflow : StatefulWorkflow<EditProps, State, Output, TodoEditScr
170170
override fun render(
171171
renderProps: Props,
172172
renderState: State,
173-
context: RenderContext
173+
context: RenderContext<EditProps, State, Output>
174174
): TodoEditScreen = TodoEditScreen(
175175
title = renderState.editedTitle,
176176
note = renderState.editedNote,
@@ -214,7 +214,7 @@ object TodoListWorkflow : StatefulWorkflow<ListProps, State, BackPressed, List<S
214214
override fun render(
215215
renderProps: ListProps,
216216
renderState: State,
217-
context: RenderContext
217+
context: RenderContext<ListProps, State, BackPressed>
218218
): List<Screen> {
219219
val titles = renderState.todos.map { it.title }
220220
return listOf(
@@ -242,7 +242,7 @@ object RootNavigationWorkflow : StatefulWorkflow<Unit, State, Nothing, BackStack
242242
override fun render(
243243
renderProps: Unit,
244244
renderState: State,
245-
context: RenderContext
245+
context: RenderContext<Unit, State, Nothing>
246246
): BackStackScreen<*> {
247247
// …
248248
@@ -332,7 +332,7 @@ object TodoListWorkflow : StatefulWorkflow<ListProps, State, Back, List<Screen>>
332332
override fun render(
333333
renderProps: ListProps,
334334
renderState: State,
335-
context: RenderContext
335+
context: RenderContext<ListProps, State, Back>
336336
): List<Screen> {
337337
val titles = renderState.todos.map { it.title }
338338
return listOf(

0 commit comments

Comments
 (0)