Skip to content
9 changes: 5 additions & 4 deletions samples/tutorial/Tutorial1.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ The `tutorial-base` module will be our starting place to build from.

Go ahead and launch `TutorialActivity`. You should see this welcome screen:

![Welcome](im "An Android phone app with title text _Welcome!_, an EditText with prompt _Please enter your name_, and a Log In button")
![Welcome](images/welcome.png "An Android phone app with title text _Welcome!_, an EditText with prompt _Please enter your name_, and a Log In button")

You can enter a name, but the login button won't do anything.

Expand Down Expand Up @@ -142,7 +142,7 @@ private fun welcomeScreenRunner(
viewBinding: WelcomeViewBinding
) = ScreenViewRunner { screen: WelcomeScreen, _ ->
viewBinding.prompt.text = screen.promptText
viewBinding.login.setOnClickListener {
viewBinding.logIn.setOnClickListener {
screen.onLogInTapped(viewBinding.username.text.toString())
}
}
Expand Down Expand Up @@ -196,7 +196,8 @@ object WelcomeWorkflow : StatefulWorkflow<Unit, State, Output, WelcomeScreen>()
}
```

> [!TIP] This tutorial doesn't cover persistence support.
> [!TIP]
> This tutorial doesn't cover persistence support.
> If you feel the need for it,
> the easiest way to get there is by using the [`@Parcelize` annotation](https://developer.android.com/kotlin/parcelize) on your state types.
> They will be saved and restored via the `savedStateHandler` of the JetPack `ViewModel`
Expand Down Expand Up @@ -372,7 +373,7 @@ You could also write:
}
```

And let's make an `onLogInTapped` event handler that enques one of those `updateName` actions.
And let's make an `onLogInTapped` event handler that enqueues one of those `updateName` actions.

```kotlin
object WelcomeWorkflow : StatefulWorkflow<Unit, State, Output, WelcomeScreen>() {
Expand Down
38 changes: 22 additions & 16 deletions samples/tutorial/Tutorial2.md
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ private fun todoListScreenRunner(
todoListBinding.todoList.adapter = adapter

return ScreenViewRunner { screen: TodoListScreen, _ ->
adapter.todoList = rendering.todoTitles
adapter.todoList = screen.todoTitles
adapter.notifyDataSetChanged()
}
}
Expand Down Expand Up @@ -208,7 +208,7 @@ object RootNavigationWorkflow : StatefulWorkflow<Unit, Unit, Nothing, Screen>()
return welcomeScreen
}

override fun snapshotState(state: State): Snapshot? = null
override fun snapshotState(state: Unit): Snapshot? = null
}
```

Expand Down Expand Up @@ -254,10 +254,10 @@ At the same time, add a `reportNavigation()` call when creating the `renderings`
workflow = RootNavigationWorkflow,
scope = viewModelScope,
savedStateHandle = savedState
)
}.reportNavigation {
Log.i("navigate", it.toString())
}
).reportNavigation {
Log.i("navigate", it.toString())
}
}
```

Now when you run the app we'll see the welcome screen again.
Expand Down Expand Up @@ -349,9 +349,9 @@ Finally, map the output event from `WelcomeWorkflow` in `RootNavigationWorkflow`
```kotlin
override fun render(
renderProps: Unit,
renderState: Unit,
renderState: State,
context: RenderContext
): Screen {
): WelcomeScreen {
// Render a child workflow of type WelcomeWorkflow. When renderChild is called, the
// infrastructure will start a child workflow session if one is not already running.
val welcomeScreen = context.renderChild(WelcomeWorkflow) { output ->
Expand Down Expand Up @@ -396,7 +396,7 @@ object RootNavigationWorkflow : StatefulWorkflow<Unit, State, Nothing, Screen>()
// infrastructure will create a child workflow with state if one is not already running.
val welcomeScreen = context.renderChild(WelcomeWorkflow) { output ->
// When WelcomeWorkflow emits LoggedIn, turn it into our login action.
login(output.username)
logIn(output.username)
}
return welcomeScreen
}
Expand Down Expand Up @@ -486,8 +486,12 @@ object TodoListWorkflow : StatefulWorkflow<ListProps, State, Nothing, TodoListSc
renderState: State,
context: RenderContext
): TodoListScreen {
username = renderProps.username,
todoTitles = titles,
val titles = renderState.todos.map { it.title }

return TodoListScreen(
username = renderProps.username,
todoTitles = titles,
)
}
```

Expand Down Expand Up @@ -571,8 +575,8 @@ object RootNavigationWorkflow : StatefulWorkflow<Unit, State, Nothing, BackStack

is ShowingTodo -> {
val todoBackStack = context.renderChild(
child = TodoNavigationWorkflow,
props = TodoProps(renderState.username),
child = TodoListWorkflow,
props = ListProps(renderState.username),
handler = {
// When TodoNavigationWorkflow emits Back, enqueue our log out action.
logOut
Expand All @@ -591,7 +595,8 @@ Update `render()` to create an `eventHandler` function to post the new output ev

At the same time, use workflow's handy `View.setBackHandler` function to respond to Android back press events.

> [!NOTE] `View.setBackHandler` is implemented via
> [!NOTE]
> `View.setBackHandler` is implemented via
> [OnBackPressedCallback](https://developer.android.com/reference/androidx/activity/OnBackPressedCallback)
> and so plays nicely with the
> [OnBackPressedDispatcher](https://developer.android.com/reference/androidx/activity/OnBackPressedDispatcher), Compose's [BackHandler](https://foso.github.io/Jetpack-Compose-Playground/activity/backhandler/)
Expand Down Expand Up @@ -653,7 +658,8 @@ navigate TodoListScreen(username=David, todoTitles=[Take the cat for a walk], o
navigate WelcomeScreen(promptText=, onLogInTapped=Function1<E, kotlin.Unit>)
```

> [!TIP] Note the logging above remains useful
> [!TIP]
> Note the logging above remains useful
> even though we are now wrapping our leaf screens in a `BackStackScreen`.
> The default `onNavigate` function used by `Flow<*>.reportNavigation()`
> can drill through the stock `Unwrappable` interface implemented by `BackStackScreen`
Expand Down Expand Up @@ -709,6 +715,6 @@ We'll show you how in the next tutorial, when we add our Todo Editing screen.
> - The `Overlay` marker interface, implemented by renderings that model things like Android `Dialog` windows
> - `ScreenOverlay` for modeling an `Overlay` whose content comes from a `Screen`
> - `BodyAndOverlaysScreen`, a class that arranges `Overlay` instances in layers over a body `Screen`.
> - And `the [AndroidOverlay](https://github.com/square/workflow-kotlin/blob/main/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/navigation/AndroidOverlay.kt)` interface that simplifies implementing `ScreenOverlay` with Android's `AppCompatDialog` class.
> - And the [`AndroidOverlay`](https://github.com/square/workflow-kotlin/blob/main/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/navigation/AndroidOverlay.kt) interface that simplifies implementing `ScreenOverlay` with Android's `AppCompatDialog` class.

[Tutorial 3](Tutorial3.md)
10 changes: 6 additions & 4 deletions samples/tutorial/Tutorial4.md
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ object TodoListWorkflow : StatefulWorkflow<ListProps, State, Output, TodoListScr
// …
}

override fun snapshotState(state: Unit): Snapshot? = null
override fun snapshotState(state: State): Snapshot? = null

// …
}
Expand Down Expand Up @@ -213,7 +213,7 @@ object TodoNavigationWorkflow : StatefulWorkflow<TodoProps, State, Back, List<Sc
val todoListScreen = context.renderChild(
TodoListWorkflow,
props = ListProps(
username = renderProps.name,
username = renderProps.username,
todos = renderState.todos
)
) { output ->
Expand Down Expand Up @@ -294,7 +294,7 @@ object TodoNavigationWorkflow : StatefulWorkflow<TodoProps, State, Back, List<Sc
val todoListScreen = context.renderChild(
TodoListWorkflow,
props = ListProps(
username = renderProps.name,
username = renderProps.username,
todos = renderState.todos
)
) { output ->
Expand Down Expand Up @@ -403,7 +403,7 @@ object TodoNavigationWorkflow : StatefulWorkflow<TodoProps, State, Back, List<Sc
val todoListScreen = context.renderChild(
TodoListWorkflow,
props = ListProps(
username = renderProps.name,
username = renderProps.username,
todos = renderState.todos
)
) { output ->
Expand Down Expand Up @@ -441,3 +441,5 @@ Additionally, now the `TodoList` and `TodoEdit` workflows are completely decoupl
there is no longer a requirement that the `TodoEdit` workflow is displayed after the list.
For instance, we could change the list to have "viewing" or "editing" modes,
where tapping on an item might only allow it to be viewed, but another mode would allow editing.

[Tutorial 5](Tutorial5.md)
13 changes: 7 additions & 6 deletions samples/tutorial/Tutorial5.md
Original file line number Diff line number Diff line change
Expand Up @@ -241,7 +241,8 @@ The child's rendering _must_ be specified when declaring an expected workflow
since the parent's call to `renderChild` _must_ return a value of the appropriate rendering type,
and the workflow library can't know how to create those instances of your own types.

> [!NOTE] Under `testRender` all children are mocked
> [!NOTE]
> Under `testRender` all children are mocked
>
> We consider tests built around `testRender` to be unit tests (as opposed to integration tests)
> because they do not actually run any child workflows or workers.
Expand Down Expand Up @@ -342,7 +343,7 @@ class TodoNavigationWorkflowTest {

TodoNavigationWorkflow
.testRender(
props = TodoProps(name = "Ada"),
props = TodoProps(username = "Ada"),
// Start from the list step to validate selecting a todo.
initialState = State(
todos = todos,
Expand Down Expand Up @@ -384,7 +385,7 @@ class TodoNavigationWorkflowTest {

TodoNavigationWorkflow
.testRender(
props = TodoProps(name = "Ada"),
props = TodoProps(username = "Ada"),
// Start from the edit step so we can simulate saving.
initialState = State(
todos = todos,
Expand Down Expand Up @@ -463,7 +464,7 @@ class TodoNavigationWorkflowTest {
The `RenderTester` allows easy "mocking" of child workflows and workers.
However, this means that we are not exercising the full infrastructure
(even though we could get a fairly high confidence from the tests).
ometimes, it may be worth putting together integration tests that test a full tree of Workflows.
Sometimes, it may be worth putting together integration tests that test a full tree of Workflows.
This lets us test integration with the non-workflow world as well,
such as external reactive data sources that your workflows might be observing via Workers.

Expand All @@ -481,12 +482,12 @@ the same runtime that `renderWorkflowIn` uses.
When you create an Android app using Workflow,
you will probably use `renderWorkflowIn`,
which starts a runtime to host your workflows in an androidx ViewModel.
Under the hood,this method is an overload of lower-level `renderWorkflowIn` function
Under the hood, this method is an overload of lower-level `renderWorkflowIn` function
that runs the workflow runtime in a coroutine and exposes a `StateFlow` of renderings.
When writing integration tests for workflows,
you can use this core function directly (maybe with a library like [Turbine](https://github.com/cashapp/turbine)),
or you can use `workflow-testing`'s `WorkflowTester`.
The `WorkflowTester` starts a workflow and lets you request renderingsand outputs manually
The `WorkflowTester` starts a workflow and lets you request renderings and outputs manually
so you can write tests that interact with the runtime from the outside.

This will be a properly opaque test,
Expand Down
Loading