Skip to content

Modernize tutorial final code #1116

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

Closed
wants to merge 3 commits into from
Closed
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
2 changes: 1 addition & 1 deletion samples/tutorial/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ To help with the setup, we have created a few helper modules:

- `tutorial-views`: A set of 3 views for the 3 screens we will be building, `Welcome`, `TodoList`,
and `TodoEdit`.
- `tutorial-base`: This is the starting point to build out the tutorial. It contains layouts that host the views from `TutorialViews` to see how they display.
- `tutorial-base`: This is the starting point to build out the tutorial.
- `tutorial-final`: This is an example of the completed tutorial - could be used as a reference if
you get stuck.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ import com.squareup.workflow1.StatefulWorkflow
import com.squareup.workflow1.action
import com.squareup.workflow1.renderChild
import com.squareup.workflow1.ui.WorkflowUiExperimentalApi
import com.squareup.workflow1.ui.backstack.BackStackScreen
import com.squareup.workflow1.ui.backstack.toBackStackScreen
import com.squareup.workflow1.ui.container.BackStackScreen
import com.squareup.workflow1.ui.container.toBackStackScreen
import workflow.tutorial.RootWorkflow.State
import workflow.tutorial.RootWorkflow.State.Todo
import workflow.tutorial.RootWorkflow.State.Welcome
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,10 @@ import androidx.lifecycle.viewModelScope
import com.squareup.workflow1.ui.ViewRegistry
import com.squareup.workflow1.ui.WorkflowLayout
import com.squareup.workflow1.ui.WorkflowUiExperimentalApi
import com.squareup.workflow1.ui.backstack.BackStackContainer
import com.squareup.workflow1.ui.renderWorkflowIn
import kotlinx.coroutines.flow.StateFlow

private val viewRegistry = ViewRegistry(
BackStackContainer,
WelcomeLayoutRunner,
TodoListLayoutRunner
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ import com.squareup.workflow1.StatefulWorkflow
import com.squareup.workflow1.action
import com.squareup.workflow1.renderChild
import com.squareup.workflow1.ui.WorkflowUiExperimentalApi
import com.squareup.workflow1.ui.backstack.BackStackScreen
import com.squareup.workflow1.ui.backstack.toBackStackScreen
import com.squareup.workflow1.ui.container.BackStackScreen
import com.squareup.workflow1.ui.container.toBackStackScreen
import workflow.tutorial.RootWorkflow.State
import workflow.tutorial.RootWorkflow.State.Todo
import workflow.tutorial.RootWorkflow.State.Welcome
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,10 @@ import androidx.lifecycle.viewModelScope
import com.squareup.workflow1.ui.ViewRegistry
import com.squareup.workflow1.ui.WorkflowLayout
import com.squareup.workflow1.ui.WorkflowUiExperimentalApi
import com.squareup.workflow1.ui.backstack.BackStackContainer
import com.squareup.workflow1.ui.renderWorkflowIn
import kotlinx.coroutines.flow.StateFlow

private val viewRegistry = ViewRegistry(
BackStackContainer,
WelcomeLayoutRunner,
TodoListLayoutRunner,
TodoEditLayoutRunner
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ import com.squareup.workflow1.StatefulWorkflow
import com.squareup.workflow1.action
import com.squareup.workflow1.renderChild
import com.squareup.workflow1.ui.WorkflowUiExperimentalApi
import com.squareup.workflow1.ui.backstack.BackStackScreen
import com.squareup.workflow1.ui.backstack.toBackStackScreen
import com.squareup.workflow1.ui.container.BackStackScreen
import com.squareup.workflow1.ui.container.toBackStackScreen
import workflow.tutorial.RootWorkflow.State
import workflow.tutorial.RootWorkflow.State.Todo
import workflow.tutorial.RootWorkflow.State.Welcome
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,10 @@ import androidx.lifecycle.viewModelScope
import com.squareup.workflow1.ui.ViewRegistry
import com.squareup.workflow1.ui.WorkflowLayout
import com.squareup.workflow1.ui.WorkflowUiExperimentalApi
import com.squareup.workflow1.ui.backstack.BackStackContainer
import com.squareup.workflow1.ui.renderWorkflowIn
import kotlinx.coroutines.flow.StateFlow

private val viewRegistry = ViewRegistry(
BackStackContainer,
WelcomeLayoutRunner,
TodoListLayoutRunner,
TodoEditLayoutRunner
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,16 @@ import com.squareup.workflow1.Snapshot
import com.squareup.workflow1.StatefulWorkflow
import com.squareup.workflow1.action
import com.squareup.workflow1.renderChild
import com.squareup.workflow1.ui.Screen
import com.squareup.workflow1.ui.WorkflowUiExperimentalApi
import com.squareup.workflow1.ui.backstack.BackStackScreen
import com.squareup.workflow1.ui.backstack.toBackStackScreen
import workflow.tutorial.RootWorkflow.State
import workflow.tutorial.RootWorkflow.State.Todo
import workflow.tutorial.RootWorkflow.State.Welcome
import workflow.tutorial.TodoWorkflow.TodoProps
import com.squareup.workflow1.ui.container.BackStackScreen
import workflow.tutorial.RootNavigationWorkflow.State
import workflow.tutorial.RootNavigationWorkflow.State.Todo
import workflow.tutorial.RootNavigationWorkflow.State.Welcome
import workflow.tutorial.TodoNavigationWorkflow.TodoProps

@OptIn(WorkflowUiExperimentalApi::class)
object RootWorkflow : StatefulWorkflow<Unit, State, Nothing, BackStackScreen<Any>>() {
object RootNavigationWorkflow : StatefulWorkflow<Unit, State, Nothing, BackStackScreen<*>>() {

sealed class State {
object Welcome : State()
Expand All @@ -30,37 +30,29 @@ object RootWorkflow : StatefulWorkflow<Unit, State, Nothing, BackStackScreen<Any
renderProps: Unit,
renderState: State,
context: RenderContext
): BackStackScreen<Any> {

// Our list of back stack items. Will always include the "WelcomeScreen".
val backstackScreens = mutableListOf<Any>()

): BackStackScreen<*> {
// Render a child workflow of type WelcomeWorkflow. When renderChild is called, the
// 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)
}
backstackScreens += welcomeScreen

when (renderState) {
return when (renderState) {
// When the state is Welcome, defer to the WelcomeWorkflow.
is Welcome -> {
// We always add the welcome screen to the backstack, so this is a no op.
BackStackScreen(welcomeScreen)
}

// When the state is Todo, defer to the TodoListWorkflow.
is Todo -> {
val todoListScreens = context.renderChild(TodoWorkflow, TodoProps(renderState.username)) {
val todoBackStack = context.renderChild(TodoNavigationWorkflow, TodoProps(renderState.username)) {
// When receiving a Back output, treat it as a logout action.
logout
}
backstackScreens.addAll(todoListScreens)
BackStackScreen(welcomeScreen) + todoBackStack
}
}

// Finally, return the BackStackScreen with a list of BackStackScreen.Items
return backstackScreens.toBackStackScreen()
}

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

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package workflow.tutorial

import com.squareup.workflow1.ui.ScreenViewRunner
import com.squareup.workflow1.ui.ViewEnvironment
import com.squareup.workflow1.ui.WorkflowUiExperimentalApi
import com.squareup.workflow1.ui.control
import com.squareup.workflow1.ui.setBackHandler
import workflow.tutorial.views.databinding.TodoEditViewBinding

@OptIn(WorkflowUiExperimentalApi::class)
class TodoEditRunner(
private val binding: TodoEditViewBinding
) : ScreenViewRunner<TodoEditScreen> {

override fun showRendering(
rendering: TodoEditScreen,
environment: ViewEnvironment
) {
binding.root.setBackHandler(rendering.onBackPressed)
binding.save.setOnClickListener { rendering.onSavePressed() }
rendering.title.control(binding.todoTitle)
rendering.note.control(binding.todoNote)
}
}
Original file line number Diff line number Diff line change
@@ -1,15 +1,21 @@
package workflow.tutorial

import com.squareup.workflow1.ui.AndroidScreen
import com.squareup.workflow1.ui.ScreenViewFactory
import com.squareup.workflow1.ui.TextController
import com.squareup.workflow1.ui.WorkflowUiExperimentalApi
import workflow.tutorial.views.databinding.TodoEditViewBinding

@OptIn(WorkflowUiExperimentalApi::class)
data class TodoEditScreen(
/** The title of this todo item. */
val title: String,
val title: TextController,
/** The contents, or "note" of the todo. */
val note: String,

/** Callbacks for when the title or note changes. */
val onTitleChanged: (String) -> Unit,
val onNoteChanged: (String) -> Unit,
val note: TextController,

val discardChanges: () -> Unit,
val saveChanges: () -> Unit
)
val onBackPressed: () -> Unit,
val onSavePressed: () -> Unit
) : AndroidScreen<TodoEditScreen> {
override val viewFactory =
ScreenViewFactory.fromViewBinding(TodoEditViewBinding::inflate, ::TodoEditRunner)
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,14 @@ package workflow.tutorial
import com.squareup.workflow1.Snapshot
import com.squareup.workflow1.StatefulWorkflow
import com.squareup.workflow1.action
import com.squareup.workflow1.ui.WorkflowUiExperimentalApi
import workflow.tutorial.TodoEditWorkflow.Output
import workflow.tutorial.TodoEditWorkflow.Output.Discard
import workflow.tutorial.TodoEditWorkflow.Output.Save
import workflow.tutorial.TodoEditWorkflow.EditProps
import workflow.tutorial.TodoEditWorkflow.State

@OptIn(WorkflowUiExperimentalApi::class)
object TodoEditWorkflow : StatefulWorkflow<EditProps, State, Output, TodoEditScreen>() {

data class EditProps(
Expand Down Expand Up @@ -54,33 +56,20 @@ object TodoEditWorkflow : StatefulWorkflow<EditProps, State, Output, TodoEditScr
return TodoEditScreen(
title = renderState.todo.title,
note = renderState.todo.note,
onTitleChanged = { context.actionSink.send(onTitleChanged(it)) },
onNoteChanged = { context.actionSink.send(onNoteChanged(it)) },
saveChanges = { context.actionSink.send(onSave()) },
discardChanges = { context.actionSink.send(onDiscard()) }
onSavePressed = { context.actionSink.send(postSave) },
onBackPressed = { context.actionSink.send(postDiscard) }
)
}

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

internal fun onTitleChanged(title: String) = action {
state = state.withTitle(title)
}

internal fun onNoteChanged(note: String) = action {
state = state.withNote(note)
}

private fun onDiscard() = action {
private val postDiscard = action {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: not understanding where the 'post' namign is coming from? I think i get that we are 'posting' either a discard or a save to our parent Workflow? Is that the intention?
I'm not sure that's helpful to me though as the name of the action.

LIke is the action about what it does (posting a save) or what it responds to (on "Save" clicked)? I feel like we've often used the latter, so maybe that's what is tripping me up. Maybe the former is preferrable?

Maybe this is all pedantic (though if there's any place to be so, it would be the tutorial...).

I, for one, long for Action suffixes when we save actions like this. As in could we use discardAction, saveAction -> telling you what it is (Action) and what it does (discard or save) but not the implementation details ('posting' to the parent).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LIke is the action about what it does (posting a save) or what it responds to (on "Save" clicked)? I feel like we've often used the latter, so maybe that's what is tripping me up. Maybe the former is preferrable?

I like the *Action notion, will adopt.

Copy link
Contributor Author

@rjrjr rjrjr Oct 24, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually…

What I liked about the awkward post* names was that they communicated what was going to happen in response to the UI event. I don't think that's an implementation detail, it helps make visible what the role of this workflow is in the overall structure. And once I've put the names in that very active format, the *Action suffix starts to sound noisy.

WDYT:

      onTodoSelected = { context.actionSink.send(requestSelection(it)) },
      onBackPressed = { context.actionSink.send(requestGoBack) },
      onAddPressed = { context.actionSink.send(requestNew) }

// Emit the Discard output when the discard action is received.
setOutput(Discard)
}

internal fun onSave() = action {
internal val postSave = action {
// Emit the Save output with the current todo state when the save action is received.
setOutput(Save(state.todo))
}

private fun State.withTitle(title: String) = copy(todo = todo.copy(title = title))
private fun State.withNote(note: String) = copy(todo = todo.copy(note = note))
}
Original file line number Diff line number Diff line change
@@ -1,14 +1,24 @@
package workflow.tutorial

import com.squareup.workflow1.ui.AndroidScreen
import com.squareup.workflow1.ui.ScreenViewFactory
import com.squareup.workflow1.ui.WorkflowUiExperimentalApi
import workflow.tutorial.views.databinding.TodoListViewBinding

/**
* This should contain all data to display in the UI.
*
* It should also contain callbacks for any UI events, for example:
* `val onButtonTapped: () -> Unit`.
*/
@OptIn(WorkflowUiExperimentalApi::class)
data class TodoListScreen(
val username: String,
val todoTitles: List<String>,
val onTodoSelected: (Int) -> Unit,
val onBack: () -> Unit
)
val onBackPressed: () -> Unit,
val onAddPressed: () -> Unit
): AndroidScreen<TodoListScreen> {
override val viewFactory =
ScreenViewFactory.fromViewBinding(TodoListViewBinding::inflate, ::TodoListScreenRunner)
}
Original file line number Diff line number Diff line change
@@ -1,19 +1,17 @@
package workflow.tutorial

import androidx.recyclerview.widget.LinearLayoutManager
import com.squareup.workflow1.ui.LayoutRunner
import com.squareup.workflow1.ui.LayoutRunner.Companion.bind
import com.squareup.workflow1.ui.ScreenViewRunner
import com.squareup.workflow1.ui.ViewEnvironment
import com.squareup.workflow1.ui.ViewFactory
import com.squareup.workflow1.ui.WorkflowUiExperimentalApi
import com.squareup.workflow1.ui.backPressedHandler
import com.squareup.workflow1.ui.setBackHandler
import workflow.tutorial.views.TodoListAdapter
import workflow.tutorial.views.databinding.TodoListViewBinding

@OptIn(WorkflowUiExperimentalApi::class)
class TodoListLayoutRunner(
class TodoListScreenRunner(
private val todoListBinding: TodoListViewBinding
) : LayoutRunner<TodoListScreen> {
) : ScreenViewRunner<TodoListScreen> {

private val adapter = TodoListAdapter()

Expand All @@ -24,9 +22,10 @@ class TodoListLayoutRunner(

override fun showRendering(
rendering: TodoListScreen,
viewEnvironment: ViewEnvironment
environment: ViewEnvironment
) {
todoListBinding.root.backPressedHandler = rendering.onBack
todoListBinding.root.setBackHandler(rendering.onBackPressed)
todoListBinding.add.setOnClickListener { rendering.onAddPressed() }

with(todoListBinding.todoListWelcome) {
text =
Expand All @@ -37,8 +36,4 @@ class TodoListLayoutRunner(
adapter.onTodoSelected = rendering.onTodoSelected
adapter.notifyDataSetChanged()
}

companion object : ViewFactory<TodoListScreen> by bind(
TodoListViewBinding::inflate, ::TodoListLayoutRunner
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,14 +29,15 @@ object TodoListWorkflow : StatelessWorkflow<ListProps, Output, TodoListScreen>()
): TodoListScreen {
val titles = renderProps.todos.map { it.title }
return TodoListScreen(
username = renderProps.username,
todoTitles = titles,
onTodoSelected = { context.actionSink.send(selectTodo(it)) },
onBack = { context.actionSink.send(onBack()) }
username = renderProps.username,
todoTitles = titles.map { it.textValue },
onTodoSelected = { context.actionSink.send(selectTodo(it)) },
onBackPressed = { context.actionSink.send(postGoBack) },
onAddPressed = { context.actionSink.send(postNewTodo) }
)
}

private fun onBack() = action {
private val postGoBack = action {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same comments.

// When an onBack action is received, emit a Back output.
setOutput(Back)
}
Expand All @@ -46,7 +47,7 @@ object TodoListWorkflow : StatelessWorkflow<ListProps, Output, TodoListScreen>()
setOutput(SelectTodo(index))
}

private fun new() = action {
private val postNewTodo = action {
// Tell our parent a new todo item should be created.
setOutput(NewTodo)
}
Expand Down
Loading