diff --git a/samples/tutorial/README.md b/samples/tutorial/README.md index d1fac33e3a..325e64ab7c 100644 --- a/samples/tutorial/README.md +++ b/samples/tutorial/README.md @@ -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. diff --git a/samples/tutorial/tutorial-2-complete/src/main/java/workflow/tutorial/RootWorkflow.kt b/samples/tutorial/tutorial-2-complete/src/main/java/workflow/tutorial/RootWorkflow.kt index 6e0e9ae48f..d8e4054091 100644 --- a/samples/tutorial/tutorial-2-complete/src/main/java/workflow/tutorial/RootWorkflow.kt +++ b/samples/tutorial/tutorial-2-complete/src/main/java/workflow/tutorial/RootWorkflow.kt @@ -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 diff --git a/samples/tutorial/tutorial-2-complete/src/main/java/workflow/tutorial/TutorialActivity.kt b/samples/tutorial/tutorial-2-complete/src/main/java/workflow/tutorial/TutorialActivity.kt index a88f619eb1..aed8f11baa 100644 --- a/samples/tutorial/tutorial-2-complete/src/main/java/workflow/tutorial/TutorialActivity.kt +++ b/samples/tutorial/tutorial-2-complete/src/main/java/workflow/tutorial/TutorialActivity.kt @@ -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 ) diff --git a/samples/tutorial/tutorial-3-complete/src/main/java/workflow/tutorial/RootWorkflow.kt b/samples/tutorial/tutorial-3-complete/src/main/java/workflow/tutorial/RootWorkflow.kt index 05ee1a4386..23559c10f9 100644 --- a/samples/tutorial/tutorial-3-complete/src/main/java/workflow/tutorial/RootWorkflow.kt +++ b/samples/tutorial/tutorial-3-complete/src/main/java/workflow/tutorial/RootWorkflow.kt @@ -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 diff --git a/samples/tutorial/tutorial-3-complete/src/main/java/workflow/tutorial/TutorialActivity.kt b/samples/tutorial/tutorial-3-complete/src/main/java/workflow/tutorial/TutorialActivity.kt index 9aa52a2e1e..42e65f358d 100644 --- a/samples/tutorial/tutorial-3-complete/src/main/java/workflow/tutorial/TutorialActivity.kt +++ b/samples/tutorial/tutorial-3-complete/src/main/java/workflow/tutorial/TutorialActivity.kt @@ -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 diff --git a/samples/tutorial/tutorial-4-complete/src/main/java/workflow/tutorial/RootWorkflow.kt b/samples/tutorial/tutorial-4-complete/src/main/java/workflow/tutorial/RootWorkflow.kt index d311bd88b5..107b1ba6c9 100644 --- a/samples/tutorial/tutorial-4-complete/src/main/java/workflow/tutorial/RootWorkflow.kt +++ b/samples/tutorial/tutorial-4-complete/src/main/java/workflow/tutorial/RootWorkflow.kt @@ -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 diff --git a/samples/tutorial/tutorial-4-complete/src/main/java/workflow/tutorial/TutorialActivity.kt b/samples/tutorial/tutorial-4-complete/src/main/java/workflow/tutorial/TutorialActivity.kt index 99c67c824c..d002274b4a 100644 --- a/samples/tutorial/tutorial-4-complete/src/main/java/workflow/tutorial/TutorialActivity.kt +++ b/samples/tutorial/tutorial-4-complete/src/main/java/workflow/tutorial/TutorialActivity.kt @@ -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 diff --git a/samples/tutorial/tutorial-final/src/main/java/workflow/tutorial/RootWorkflow.kt b/samples/tutorial/tutorial-final/src/main/java/workflow/tutorial/RootWorkflow.kt index 3a52a96006..db5b344b94 100644 --- a/samples/tutorial/tutorial-final/src/main/java/workflow/tutorial/RootWorkflow.kt +++ b/samples/tutorial/tutorial-final/src/main/java/workflow/tutorial/RootWorkflow.kt @@ -4,16 +4,17 @@ 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 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 import workflow.tutorial.TodoWorkflow.TodoProps @OptIn(WorkflowUiExperimentalApi::class) -object RootWorkflow : StatefulWorkflow>() { +object RootWorkflow : StatefulWorkflow>() { sealed class State { object Welcome : State() @@ -30,10 +31,10 @@ object RootWorkflow : StatefulWorkflow { + ): BackStackScreen { // Our list of back stack items. Will always include the "WelcomeScreen". - val backstackScreens = mutableListOf() + val backstackScreens = mutableListOf() // 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. diff --git a/samples/tutorial/tutorial-final/src/main/java/workflow/tutorial/TodoEditLayoutRunner.kt b/samples/tutorial/tutorial-final/src/main/java/workflow/tutorial/TodoEditLayoutRunner.kt deleted file mode 100644 index ef67bddd96..0000000000 --- a/samples/tutorial/tutorial-final/src/main/java/workflow/tutorial/TodoEditLayoutRunner.kt +++ /dev/null @@ -1,33 +0,0 @@ -package workflow.tutorial - -import com.squareup.workflow1.ui.LayoutRunner -import com.squareup.workflow1.ui.LayoutRunner.Companion.bind -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.setTextChangedListener -import com.squareup.workflow1.ui.updateText -import workflow.tutorial.views.databinding.TodoEditViewBinding - -@OptIn(WorkflowUiExperimentalApi::class) -class TodoEditLayoutRunner( - private val binding: TodoEditViewBinding -) : LayoutRunner { - - override fun showRendering( - rendering: TodoEditScreen, - viewEnvironment: ViewEnvironment - ) { - binding.root.backPressedHandler = rendering.discardChanges - binding.save.setOnClickListener { rendering.saveChanges() } - binding.todoTitle.updateText(rendering.title) - binding.todoTitle.setTextChangedListener { rendering.onTitleChanged(it.toString()) } - binding.todoNote.updateText(rendering.note) - binding.todoNote.setTextChangedListener { rendering.onNoteChanged(it.toString()) } - } - - companion object : ViewFactory by bind( - TodoEditViewBinding::inflate, ::TodoEditLayoutRunner - ) -} diff --git a/samples/tutorial/tutorial-final/src/main/java/workflow/tutorial/TodoEditRunner.kt b/samples/tutorial/tutorial-final/src/main/java/workflow/tutorial/TodoEditRunner.kt new file mode 100644 index 0000000000..46b8d40c60 --- /dev/null +++ b/samples/tutorial/tutorial-final/src/main/java/workflow/tutorial/TodoEditRunner.kt @@ -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 { + + override fun showRendering( + rendering: TodoEditScreen, + environment: ViewEnvironment + ) { + binding.root.setBackHandler(rendering.onBackClick) + binding.save.setOnClickListener { rendering.onSaveClick() } + rendering.title.control(binding.todoTitle) + rendering.note.control(binding.todoNote) + } +} diff --git a/samples/tutorial/tutorial-final/src/main/java/workflow/tutorial/TodoEditScreen.kt b/samples/tutorial/tutorial-final/src/main/java/workflow/tutorial/TodoEditScreen.kt index b64bb8798d..a4c892ee04 100644 --- a/samples/tutorial/tutorial-final/src/main/java/workflow/tutorial/TodoEditScreen.kt +++ b/samples/tutorial/tutorial-final/src/main/java/workflow/tutorial/TodoEditScreen.kt @@ -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 onBackClick: () -> Unit, + val onSaveClick: () -> Unit +) : AndroidScreen { + override val viewFactory = + ScreenViewFactory.fromViewBinding(TodoEditViewBinding::inflate, ::TodoEditRunner) +} diff --git a/samples/tutorial/tutorial-final/src/main/java/workflow/tutorial/TodoEditWorkflow.kt b/samples/tutorial/tutorial-final/src/main/java/workflow/tutorial/TodoEditWorkflow.kt index d0f2c48d96..a3d36015f5 100644 --- a/samples/tutorial/tutorial-final/src/main/java/workflow/tutorial/TodoEditWorkflow.kt +++ b/samples/tutorial/tutorial-final/src/main/java/workflow/tutorial/TodoEditWorkflow.kt @@ -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() { data class EditProps( @@ -54,33 +56,20 @@ object TodoEditWorkflow : StatefulWorkflow Unit`. */ +@OptIn(WorkflowUiExperimentalApi::class) data class TodoListScreen( val username: String, val todoTitles: List, val onTodoSelected: (Int) -> Unit, - val onBack: () -> Unit -) + val onBackClick: () -> Unit, + val onAddClick: () -> Unit +): AndroidScreen { + override val viewFactory = + ScreenViewFactory.fromViewBinding(TodoListViewBinding::inflate, ::TodoListScreenRunner) +} diff --git a/samples/tutorial/tutorial-final/src/main/java/workflow/tutorial/TodoListLayoutRunner.kt b/samples/tutorial/tutorial-final/src/main/java/workflow/tutorial/TodoListScreenRunner.kt similarity index 65% rename from samples/tutorial/tutorial-final/src/main/java/workflow/tutorial/TodoListLayoutRunner.kt rename to samples/tutorial/tutorial-final/src/main/java/workflow/tutorial/TodoListScreenRunner.kt index 7f1b4e85e4..56359d9d67 100644 --- a/samples/tutorial/tutorial-final/src/main/java/workflow/tutorial/TodoListLayoutRunner.kt +++ b/samples/tutorial/tutorial-final/src/main/java/workflow/tutorial/TodoListScreenRunner.kt @@ -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 { +) : ScreenViewRunner { private val adapter = TodoListAdapter() @@ -24,9 +22,10 @@ class TodoListLayoutRunner( override fun showRendering( rendering: TodoListScreen, - viewEnvironment: ViewEnvironment + environment: ViewEnvironment ) { - todoListBinding.root.backPressedHandler = rendering.onBack + todoListBinding.root.setBackHandler(rendering.onBackClick) + todoListBinding.add.setOnClickListener { rendering.onAddClick() } with(todoListBinding.todoListWelcome) { text = resources.getString(R.string.todo_list_welcome, rendering.username) @@ -36,8 +35,4 @@ class TodoListLayoutRunner( adapter.onTodoSelected = rendering.onTodoSelected adapter.notifyDataSetChanged() } - - companion object : ViewFactory by bind( - TodoListViewBinding::inflate, ::TodoListLayoutRunner - ) } diff --git a/samples/tutorial/tutorial-final/src/main/java/workflow/tutorial/TodoListWorkflow.kt b/samples/tutorial/tutorial-final/src/main/java/workflow/tutorial/TodoListWorkflow.kt index b492cdfb05..af9eb23a4f 100644 --- a/samples/tutorial/tutorial-final/src/main/java/workflow/tutorial/TodoListWorkflow.kt +++ b/samples/tutorial/tutorial-final/src/main/java/workflow/tutorial/TodoListWorkflow.kt @@ -29,14 +29,15 @@ object TodoListWorkflow : StatelessWorkflow() ): 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)) }, + onBackClick = { context.actionSink.send(postGoBack) }, + onAddClick = { context.actionSink.send(postNewTodo) } ) } - private fun onBack() = action { + private val postGoBack = action { // When an onBack action is received, emit a Back output. setOutput(Back) } @@ -46,7 +47,7 @@ object TodoListWorkflow : StatelessWorkflow() setOutput(SelectTodo(index)) } - private fun new() = action { + private val postNewTodo = action { // Tell our parent a new todo item should be created. setOutput(NewTodo) } diff --git a/samples/tutorial/tutorial-final/src/main/java/workflow/tutorial/TodoModel.kt b/samples/tutorial/tutorial-final/src/main/java/workflow/tutorial/TodoModel.kt index d9eb22fef2..5461761ed8 100644 --- a/samples/tutorial/tutorial-final/src/main/java/workflow/tutorial/TodoModel.kt +++ b/samples/tutorial/tutorial-final/src/main/java/workflow/tutorial/TodoModel.kt @@ -1,6 +1,15 @@ package workflow.tutorial +import com.squareup.workflow1.ui.TextController +import com.squareup.workflow1.ui.WorkflowUiExperimentalApi + +@OptIn(WorkflowUiExperimentalApi::class) data class TodoModel( - val title: String, - val note: String -) + val title: TextController, + val note: TextController +) { + constructor( + title: String, + note: String + ) : this(TextController(title), TextController(note)) +} diff --git a/samples/tutorial/tutorial-final/src/main/java/workflow/tutorial/TodoWorkflow.kt b/samples/tutorial/tutorial-final/src/main/java/workflow/tutorial/TodoWorkflow.kt index 287e37ec0b..7ae79932d6 100644 --- a/samples/tutorial/tutorial-final/src/main/java/workflow/tutorial/TodoWorkflow.kt +++ b/samples/tutorial/tutorial-final/src/main/java/workflow/tutorial/TodoWorkflow.kt @@ -3,6 +3,7 @@ package workflow.tutorial import com.squareup.workflow1.Snapshot import com.squareup.workflow1.StatefulWorkflow import com.squareup.workflow1.action +import com.squareup.workflow1.ui.Screen import com.squareup.workflow1.ui.WorkflowUiExperimentalApi import workflow.tutorial.TodoEditWorkflow.EditProps import workflow.tutorial.TodoEditWorkflow.Output.Discard @@ -17,7 +18,7 @@ import workflow.tutorial.TodoWorkflow.State.Step import workflow.tutorial.TodoWorkflow.TodoProps @OptIn(WorkflowUiExperimentalApi::class) -object TodoWorkflow : StatefulWorkflow>() { +object TodoWorkflow : StatefulWorkflow>() { data class TodoProps(val name: String) @@ -43,27 +44,27 @@ object TodoWorkflow : StatefulWorkflow>() { props: TodoProps, snapshot: Snapshot? ) = State( - todos = listOf( - TodoModel( - title = "Take the cat for a walk", - note = "Cats really need their outside sunshine time. Don't forget to walk " + - "Charlie. Hamilton is less excited about the prospect." - ) - ), - step = Step.List + todos = listOf( + TodoModel( + title = "Take the cat for a walk", + note = "Cats really need their outside sunshine time. Don't forget to walk " + + "Charlie. Hamilton is less excited about the prospect." + ) + ), + step = Step.List ) override fun render( renderProps: TodoProps, renderState: State, context: RenderContext - ): List { + ): List { val todoListScreen = context.renderChild( - TodoListWorkflow, - props = ListProps( - username = renderProps.name, - todos = renderState.todos - ) + TodoListWorkflow, + props = ListProps( + username = renderProps.name, + todos = renderState.todos + ) ) { output -> when (output) { Output.Back -> onBack() @@ -78,8 +79,8 @@ object TodoWorkflow : StatefulWorkflow>() { is Step.Edit -> { // On the "edit" step, return both the list and edit screens. val todoEditScreen = context.renderChild( - TodoEditWorkflow, - EditProps(renderState.todos[step.index]) + TodoEditWorkflow, + EditProps(renderState.todos[step.index]) ) { output -> when (output) { // Send the discardChanges action when the discard output is received. @@ -108,10 +109,10 @@ object TodoWorkflow : StatefulWorkflow>() { private fun newTodo() = action { // Append a new todo model to the end of the list. state = state.copy( - todos = state.todos + TodoModel( - title = "New Todo", - note = "" - ) + todos = state.todos + TodoModel( + title = "New Todo", + note = "" + ) ) } @@ -126,8 +127,8 @@ object TodoWorkflow : StatefulWorkflow>() { ) = action { // When changes are saved, update the state of that todo item and return to the list. state = state.copy( - todos = state.todos.toMutableList().also { it[index] = todo }, - step = Step.List + todos = state.todos.toMutableList().also { it[index] = todo }, + step = Step.List ) } } diff --git a/samples/tutorial/tutorial-final/src/main/java/workflow/tutorial/TutorialActivity.kt b/samples/tutorial/tutorial-final/src/main/java/workflow/tutorial/TutorialActivity.kt index 99c67c824c..86071497fc 100644 --- a/samples/tutorial/tutorial-final/src/main/java/workflow/tutorial/TutorialActivity.kt +++ b/samples/tutorial/tutorial-final/src/main/java/workflow/tutorial/TutorialActivity.kt @@ -1,5 +1,3 @@ -@file:OptIn(WorkflowUiExperimentalApi::class) - package workflow.tutorial import android.os.Bundle @@ -8,20 +6,13 @@ import androidx.appcompat.app.AppCompatActivity import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope -import com.squareup.workflow1.ui.ViewRegistry +import com.squareup.workflow1.ui.Screen 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 -) - +@OptIn(WorkflowUiExperimentalApi::class) class TutorialActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { @@ -32,17 +23,19 @@ class TutorialActivity : AppCompatActivity() { val model: TutorialViewModel by viewModels() setContentView( - WorkflowLayout(this).apply { start(model.renderings, viewRegistry) } + WorkflowLayout(this).apply { + take(lifecycle, model.renderings) + } ) } -} -class TutorialViewModel(savedState: SavedStateHandle) : ViewModel() { - val renderings: StateFlow by lazy { - renderWorkflowIn( - workflow = RootWorkflow, - scope = viewModelScope, - savedStateHandle = savedState - ) + class TutorialViewModel(savedState: SavedStateHandle) : ViewModel() { + val renderings: StateFlow by lazy { + renderWorkflowIn( + workflow = RootWorkflow, + scope = viewModelScope, + savedStateHandle = savedState + ) + } } } diff --git a/samples/tutorial/tutorial-final/src/main/java/workflow/tutorial/WelcomeLayoutRunner.kt b/samples/tutorial/tutorial-final/src/main/java/workflow/tutorial/WelcomeLayoutRunner.kt deleted file mode 100644 index 7eb9af6340..0000000000 --- a/samples/tutorial/tutorial-final/src/main/java/workflow/tutorial/WelcomeLayoutRunner.kt +++ /dev/null @@ -1,38 +0,0 @@ -package workflow.tutorial - -import com.squareup.workflow1.ui.LayoutRunner -import com.squareup.workflow1.ui.LayoutRunner.Companion.bind -import com.squareup.workflow1.ui.ViewEnvironment -import com.squareup.workflow1.ui.ViewFactory -import com.squareup.workflow1.ui.WorkflowUiExperimentalApi -import com.squareup.workflow1.ui.setTextChangedListener -import com.squareup.workflow1.ui.updateText -import workflow.tutorial.views.databinding.WelcomeViewBinding - -@OptIn(WorkflowUiExperimentalApi::class) -class WelcomeLayoutRunner( - private val welcomeBinding: WelcomeViewBinding -) : LayoutRunner { - - override fun showRendering( - rendering: WelcomeScreen, - viewEnvironment: ViewEnvironment - ) { - // updateText and setTextChangedListener are helpers provided by the workflow library that take - // care of the complexity of correctly interacting with EditTexts in a declarative manner. - welcomeBinding.username.updateText(rendering.username) - welcomeBinding.username.setTextChangedListener { - rendering.onUsernameChanged(it.toString()) - } - welcomeBinding.login.setOnClickListener { rendering.onLoginTapped() } - } - - /** - * Define a [ViewFactory] that will inflate an instance of [WelcomeViewBinding] and an instance - * of [WelcomeLayoutRunner] when asked, then wire them up so that [showRendering] will be called - * whenever the workflow emits a new [WelcomeScreen]. - */ - companion object : ViewFactory by bind( - WelcomeViewBinding::inflate, ::WelcomeLayoutRunner - ) -} diff --git a/samples/tutorial/tutorial-final/src/main/java/workflow/tutorial/WelcomeScreen.kt b/samples/tutorial/tutorial-final/src/main/java/workflow/tutorial/WelcomeScreen.kt index af5918e4bc..28f868dc5e 100644 --- a/samples/tutorial/tutorial-final/src/main/java/workflow/tutorial/WelcomeScreen.kt +++ b/samples/tutorial/tutorial-final/src/main/java/workflow/tutorial/WelcomeScreen.kt @@ -1,10 +1,18 @@ 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.WelcomeViewBinding + +@OptIn(WorkflowUiExperimentalApi::class) data class WelcomeScreen( /** The current name that has been entered. */ - val username: String, - /** Callback when the name changes in the UI. */ - val onUsernameChanged: (String) -> Unit, + val username: TextController, /** Callback when the login button is tapped. */ val onLoginTapped: () -> Unit -) +) : AndroidScreen { + override val viewFactory = + ScreenViewFactory.fromViewBinding(WelcomeViewBinding::inflate, ::WelcomeScreenRunner) +} diff --git a/samples/tutorial/tutorial-final/src/main/java/workflow/tutorial/WelcomeScreenRunner.kt b/samples/tutorial/tutorial-final/src/main/java/workflow/tutorial/WelcomeScreenRunner.kt new file mode 100644 index 0000000000..92a9695747 --- /dev/null +++ b/samples/tutorial/tutorial-final/src/main/java/workflow/tutorial/WelcomeScreenRunner.kt @@ -0,0 +1,23 @@ +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 workflow.tutorial.views.databinding.WelcomeViewBinding + +@OptIn(WorkflowUiExperimentalApi::class) +class WelcomeScreenRunner( + private val welcomeBinding: WelcomeViewBinding +) : ScreenViewRunner { + + override fun showRendering( + rendering: WelcomeScreen, + environment: ViewEnvironment + ) { + // TextController is a helper provided by the workflow library that takes + // care of the complexity of correctly interacting with EditTexts in a declarative manner. + rendering.username.control(welcomeBinding.username) + welcomeBinding.login.setOnClickListener { rendering.onLoginTapped() } + } +} diff --git a/samples/tutorial/tutorial-final/src/main/java/workflow/tutorial/WelcomeWorkflow.kt b/samples/tutorial/tutorial-final/src/main/java/workflow/tutorial/WelcomeWorkflow.kt index d59983c393..95feba0664 100644 --- a/samples/tutorial/tutorial-final/src/main/java/workflow/tutorial/WelcomeWorkflow.kt +++ b/samples/tutorial/tutorial-final/src/main/java/workflow/tutorial/WelcomeWorkflow.kt @@ -3,13 +3,16 @@ package workflow.tutorial import com.squareup.workflow1.Snapshot import com.squareup.workflow1.StatefulWorkflow import com.squareup.workflow1.action +import com.squareup.workflow1.ui.TextController +import com.squareup.workflow1.ui.WorkflowUiExperimentalApi import workflow.tutorial.WelcomeWorkflow.LoggedIn import workflow.tutorial.WelcomeWorkflow.State +@OptIn(WorkflowUiExperimentalApi::class) object WelcomeWorkflow : StatefulWorkflow() { data class State( - val name: String + val name: TextController ) data class LoggedIn(val username: String) @@ -17,30 +20,24 @@ object WelcomeWorkflow : StatefulWorkflow( override fun initialState( props: Unit, snapshot: Snapshot? - ): State = State(name = "") + ): State = State(name = TextController("")) override fun render( renderProps: Unit, renderState: State, context: RenderContext ): WelcomeScreen = WelcomeScreen( - username = renderState.name, - onUsernameChanged = { context.actionSink.send(onNameChanged(it)) }, - onLoginTapped = { - // Whenever the login button is tapped, emit the onLogin action. - context.actionSink.send(onLogin()) - } + username = renderState.name, + onLoginTapped = { + // Whenever the login button is tapped, emit the onLogin action. + context.actionSink.send(onLogin()) + } ) - // Needs to be internal so we can access it from the tests. - internal fun onNameChanged(name: String) = action { - state = state.copy(name = name) - } - internal fun onLogin() = action { // Don't log in if the name isn't filled in. - if (state.name.isNotEmpty()) { - setOutput(LoggedIn(state.name)) + state.name.textValue.takeIf { it.isNotEmpty() }?.let { + setOutput(LoggedIn(it)) } } diff --git a/samples/tutorial/tutorial-final/src/test/java/workflow/tutorial/RootWorkflowTest.kt b/samples/tutorial/tutorial-final/src/test/java/workflow/tutorial/RootWorkflowTest.kt index f7446036f1..e83b8fb5bb 100644 --- a/samples/tutorial/tutorial-final/src/test/java/workflow/tutorial/RootWorkflowTest.kt +++ b/samples/tutorial/tutorial-final/src/test/java/workflow/tutorial/RootWorkflowTest.kt @@ -4,8 +4,9 @@ import com.squareup.workflow1.WorkflowOutput import com.squareup.workflow1.testing.expectWorkflow import com.squareup.workflow1.testing.launchForTestingFromStartWith import com.squareup.workflow1.testing.testRender +import com.squareup.workflow1.ui.TextController import com.squareup.workflow1.ui.WorkflowUiExperimentalApi -import com.squareup.workflow1.ui.backstack.BackStackScreen +import com.squareup.workflow1.ui.container.BackStackScreen import workflow.tutorial.RootWorkflow.State.Todo import workflow.tutorial.RootWorkflow.State.Welcome import workflow.tutorial.WelcomeWorkflow.LoggedIn @@ -27,8 +28,7 @@ class RootWorkflowTest { .expectWorkflow( workflowType = WelcomeWorkflow::class, rendering = WelcomeScreen( - username = "Ada", - onUsernameChanged = {}, + username = TextController("Ada"), onLoginTapped = {} ) ) @@ -39,7 +39,7 @@ class RootWorkflowTest { assertEquals(1, backstack.size) val welcomeScreen = backstack[0] as WelcomeScreen - assertEquals("Ada", welcomeScreen.username) + assertEquals("Ada", welcomeScreen.username.textValue) } // Assert that no action was produced during this render, meaning our state remains unchanged .verifyActionResult { _, output -> @@ -55,8 +55,7 @@ class RootWorkflowTest { .expectWorkflow( workflowType = WelcomeWorkflow::class, rendering = WelcomeScreen( - username = "Ada", - onUsernameChanged = {}, + username = TextController("Ada"), onLoginTapped = {} ), // Simulate the WelcomeWorkflow sending an output of LoggedIn as if the "log in" button @@ -70,7 +69,7 @@ class RootWorkflowTest { assertEquals(1, backstack.size) val welcomeScreen = backstack[0] as WelcomeScreen - assertEquals("Ada", welcomeScreen.username) + assertEquals("Ada", welcomeScreen.username.textValue) } // Assert that the state transitioned to Todo. .verifyActionResult { newState, _ -> @@ -89,15 +88,8 @@ class RootWorkflowTest { assertEquals(1, rendering.frames.size) val welcomeScreen = rendering.frames[0] as WelcomeScreen - // Enter a name. - welcomeScreen.onUsernameChanged("Ada") - } - - // Log in and go to the todo list. - awaitNextRendering().let { rendering -> - assertEquals(1, rendering.frames.size) - val welcomeScreen = rendering.frames[0] as WelcomeScreen - + // Enter a name and tap login + welcomeScreen.username.textValue = "Ada" welcomeScreen.onLoginTapped() } @@ -119,19 +111,9 @@ class RootWorkflowTest { assertTrue(rendering.frames[1] is TodoListScreen) val editScreen = rendering.frames[2] as TodoEditScreen - // Update the title. - editScreen.onTitleChanged("New Title") - } - - // Save the selected todo. - awaitNextRendering().let { rendering -> - assertEquals(3, rendering.frames.size) - assertTrue(rendering.frames[0] is WelcomeScreen) - assertTrue(rendering.frames[1] is TodoListScreen) - val editScreen = rendering.frames[2] as TodoEditScreen - - // Save the changes by tapping the save button. - editScreen.saveChanges() + // Enter a title and save. + editScreen.title.textValue = "New Title" + editScreen.onSaveClick() } // Expect the todo list. Validate the title was updated. diff --git a/samples/tutorial/tutorial-final/src/test/java/workflow/tutorial/TodoEditWorkflowTest.kt b/samples/tutorial/tutorial-final/src/test/java/workflow/tutorial/TodoEditWorkflowTest.kt index 1572de6230..cdedc47c99 100644 --- a/samples/tutorial/tutorial-final/src/test/java/workflow/tutorial/TodoEditWorkflowTest.kt +++ b/samples/tutorial/tutorial-final/src/test/java/workflow/tutorial/TodoEditWorkflowTest.kt @@ -1,49 +1,29 @@ package workflow.tutorial import com.squareup.workflow1.applyTo +import com.squareup.workflow1.ui.WorkflowUiExperimentalApi import org.junit.Test import workflow.tutorial.TodoEditWorkflow.EditProps import workflow.tutorial.TodoEditWorkflow.Output.Save import workflow.tutorial.TodoEditWorkflow.State import kotlin.test.assertEquals -import kotlin.test.assertNull +@OptIn(WorkflowUiExperimentalApi::class) class TodoEditWorkflowTest { // Start with a todo of "Title" "Note" private val startState = State(todo = TodoModel(title = "Title", note = "Note")) - @Test fun `title is updated`() { - // These will be ignored by the action. - val props = EditProps(TodoModel(title = "", note = "")) - - // Update the title to "Updated Title" - val (newState, actionApplied) = TodoEditWorkflow.onTitleChanged("Updated Title") - .applyTo(props, startState) - - assertNull(actionApplied.output) - assertEquals(TodoModel(title = "Updated Title", note = "Note"), newState.todo) - } - - @Test fun `note is updated`() { - // These will be ignored by the action. - val props = EditProps(TodoModel(title = "", note = "")) - - // Update the note to "Updated Note" - val (newState, actionApplied) = TodoEditWorkflow.onNoteChanged("Updated Note") - .applyTo(props, startState) - - assertNull(actionApplied.output) - assertEquals(TodoModel(title = "Title", note = "Updated Note"), newState.todo) - } - @Test fun `save emits model`() { val props = EditProps(TodoModel(title = "Title", note = "Note")) - val (_, actionApplied) = TodoEditWorkflow.onSave() + val (_, actionApplied) = TodoEditWorkflow.postSave .applyTo(props, startState) - assertEquals(Save(TodoModel(title = "Title", note = "Note")), actionApplied.output?.value) + val expected = Save(TodoModel(title = "Title", note = "Note")).todo + val actual = (actionApplied.output?.value as Save).todo + assertEquals(expected.title.textValue, actual.title.textValue) + assertEquals(expected.note.textValue, actual.note.textValue) } @Test fun `changed props updated local state`() { @@ -51,22 +31,22 @@ class TodoEditWorkflowTest { var state = TodoEditWorkflow.initialState(initialProps, null) // The initial state is a copy of the provided todo: - assertEquals("Title", state.todo.title) - assertEquals("Note", state.todo.note) + assertEquals("Title", state.todo.title.textValue) + assertEquals("Note", state.todo.note.textValue) // Create a new internal state, simulating the change from actions: state = State(TodoModel(title = "Updated Title", note = "Note")) // Update the workflow properties with the same value. The state should not be updated: state = TodoEditWorkflow.onPropsChanged(initialProps, initialProps, state) - assertEquals("Updated Title", state.todo.title) - assertEquals("Note", state.todo.note) + assertEquals("Updated Title", state.todo.title.textValue) + assertEquals("Note", state.todo.note.textValue) // The parent provided different properties. The internal state should be updated with the // newly-provided properties. val updatedProps = EditProps(initialTodo = TodoModel(title = "New Title", note = "New Note")) state = TodoEditWorkflow.onPropsChanged(initialProps, updatedProps, state) - assertEquals("New Title", state.todo.title) - assertEquals("New Note", state.todo.note) + assertEquals("New Title", state.todo.title.textValue) + assertEquals("New Note", state.todo.note.textValue) } } diff --git a/samples/tutorial/tutorial-final/src/test/java/workflow/tutorial/TodoWorkflowTest.kt b/samples/tutorial/tutorial-final/src/test/java/workflow/tutorial/TodoWorkflowTest.kt index 0cddadd52b..9214615a4f 100644 --- a/samples/tutorial/tutorial-final/src/test/java/workflow/tutorial/TodoWorkflowTest.kt +++ b/samples/tutorial/tutorial-final/src/test/java/workflow/tutorial/TodoWorkflowTest.kt @@ -3,6 +3,7 @@ package workflow.tutorial import com.squareup.workflow1.WorkflowOutput import com.squareup.workflow1.testing.expectWorkflow import com.squareup.workflow1.testing.testRender +import com.squareup.workflow1.ui.TextController import com.squareup.workflow1.ui.WorkflowUiExperimentalApi import workflow.tutorial.TodoEditWorkflow.Output.Save import workflow.tutorial.TodoListWorkflow.Output.SelectTodo @@ -20,102 +21,118 @@ class TodoWorkflowTest { val todos = listOf(TodoModel(title = "Title", note = "Note")) TodoWorkflow - .testRender( - props = TodoProps(name = "Ada"), - // Start from the list step to validate selecting a todo. - initialState = State( - todos = todos, - step = List - ) + .testRender( + props = TodoProps(name = "Ada"), + // Start from the list step to validate selecting a todo. + initialState = State( + todos = todos, + step = List ) - // We only expect the TodoListWorkflow to be rendered. - .expectWorkflow( - workflowType = TodoListWorkflow::class, - rendering = TodoListScreen( - username = "", - todoTitles = listOf("Title"), - onTodoSelected = {}, - onBack = {} - ), - // Simulate selecting the first todo. - output = WorkflowOutput(SelectTodo(index = 0)) + ) + // We only expect the TodoListWorkflow to be rendered. + .expectWorkflow( + workflowType = TodoListWorkflow::class, + rendering = TodoListScreen( + username = "", + todoTitles = listOf("Title"), + onTodoSelected = {}, + onBackClick = {}, + onAddClick = {} + ), + // Simulate selecting the first todo. + output = WorkflowOutput(SelectTodo(index = 0)) + ) + .render { rendering -> + // Just validate that there is one item in the back stack. + // Additional validation could be done on the screens returned, if desired. + assertEquals(1, rendering.size) + } + // Assert that the state was updated after the render pass with the output from the + // TodoListWorkflow. + .verifyActionResult { newState, _ -> + assertEqualState( + State( + todos = listOf(TodoModel(title = "Title", note = "Note")), + step = Edit(0) + ), newState ) - .render { rendering -> - // Just validate that there is one item in the back stack. - // Additional validation could be done on the screens returned, if desired. - assertEquals(1, rendering.size) - } - // Assert that the state was updated after the render pass with the output from the - // TodoListWorkflow. - .verifyActionResult { newState, _ -> - assertEquals( - State( - todos = listOf(TodoModel(title = "Title", note = "Note")), - step = Edit(0) - ), - newState - ) - } + } } @Test fun `saving todo`() { val todos = listOf(TodoModel(title = "Title", note = "Note")) TodoWorkflow - .testRender( - props = TodoProps(name = "Ada"), - // Start from the edit step so we can simulate saving. - initialState = State( - todos = todos, - step = Edit(index = 0) - ) + .testRender( + props = TodoProps(name = "Ada"), + // Start from the edit step so we can simulate saving. + initialState = State( + todos = todos, + step = Edit(index = 0) ) - // We always expect the TodoListWorkflow to be rendered. - .expectWorkflow( - workflowType = TodoListWorkflow::class, - rendering = TodoListScreen( - username = "", - todoTitles = listOf("Title"), - onTodoSelected = {}, - onBack = {} - ) + ) + // We always expect the TodoListWorkflow to be rendered. + .expectWorkflow( + workflowType = TodoListWorkflow::class, + rendering = TodoListScreen( + username = "", + todoTitles = listOf("Title"), + onTodoSelected = {}, + onBackClick = {}, + onAddClick = {} ) - // Expect the TodoEditWorkflow to be rendered as well (as we're on the edit step). - .expectWorkflow( - workflowType = TodoEditWorkflow::class, - rendering = TodoEditScreen( - title = "Title", - note = "Note", - onTitleChanged = {}, - onNoteChanged = {}, - discardChanges = {}, - saveChanges = {} - ), - // Simulate it emitting an output of `.save` to update the state. - output = WorkflowOutput( - Save( - TodoModel( - title = "Updated Title", - note = "Updated Note" - ) - ) + ) + // Expect the TodoEditWorkflow to be rendered as well (as we're on the edit step). + .expectWorkflow( + workflowType = TodoEditWorkflow::class, + rendering = TodoEditScreen( + title = TextController("Title"), + note = TextController("Note"), + onBackClick = {}, + onSaveClick = {} + ), + // Simulate it emitting an output of `.save` to update the state. + output = WorkflowOutput( + Save( + TodoModel( + title = "Updated Title", + note = "Updated Note" ) - ) - .render { rendering -> - // Just validate that there are two items in the back stack. - // Additional validation could be done on the screens returned, if desired. - assertEquals(2, rendering.size) - } - // Validate that the state was updated after the render pass with the output from the - // TodoEditWorkflow. - .verifyActionResult { newState, _ -> - assertEquals( - State( - todos = listOf(TodoModel(title = "Updated Title", note = "Updated Note")), - step = List - ), - newState ) - } + ) + ) + .render { rendering -> + // Just validate that there are two items in the back stack. + // Additional validation could be done on the screens returned, if desired. + assertEquals(2, rendering.size) + } + // Validate that the state was updated after the render pass with the output from the + // TodoEditWorkflow. + .verifyActionResult { newState, _ -> + assertEqualState( + State( + todos = listOf(TodoModel(title = "Updated Title", note = "Updated Note")), + step = List + ), + newState + ) + } + } + + private fun assertEqualState(expected: State, actual: State) { + assertEquals(expected.todos.size, actual.todos.size) + expected.todos.forEachIndexed { index, todo -> + assertEquals( + expected.todos[index].title.textValue, + actual.todos[index].title.textValue, + "todos[$index].title" + ) + assertEquals( + expected.todos[index].note.textValue, + actual.todos[index].note.textValue, + "todos[$index].note" + ) + } + assertEquals(expected.step, actual.step) } } diff --git a/samples/tutorial/tutorial-final/src/test/java/workflow/tutorial/WelcomeWorkflowTest.kt b/samples/tutorial/tutorial-final/src/test/java/workflow/tutorial/WelcomeWorkflowTest.kt index c39748c4df..99ebf9b370 100644 --- a/samples/tutorial/tutorial-final/src/test/java/workflow/tutorial/WelcomeWorkflowTest.kt +++ b/samples/tutorial/tutorial-final/src/test/java/workflow/tutorial/WelcomeWorkflowTest.kt @@ -2,29 +2,20 @@ package workflow.tutorial import com.squareup.workflow1.applyTo import com.squareup.workflow1.testing.testRender +import com.squareup.workflow1.ui.TextController +import com.squareup.workflow1.ui.WorkflowUiExperimentalApi import org.junit.Test import workflow.tutorial.WelcomeWorkflow.LoggedIn import kotlin.test.assertEquals import kotlin.test.assertNull +@OptIn(WorkflowUiExperimentalApi::class) class WelcomeWorkflowTest { // region Actions - @Test fun `name updates`() { - val startState = WelcomeWorkflow.State("") - val action = WelcomeWorkflow.onNameChanged("myName") - val (state, actionApplied) = action.applyTo(state = startState, props = Unit) - - // No output is expected when the name changes. - assertNull(actionApplied.output) - - // The name has been updated from the action. - assertEquals("myName", state.name) - } - @Test fun `login works`() { - val startState = WelcomeWorkflow.State("myName") + val startState = WelcomeWorkflow.State(TextController("myName")) val action = WelcomeWorkflow.onLogin() val (_, actionApplied) = action.applyTo(state = startState, props = Unit) @@ -33,14 +24,14 @@ class WelcomeWorkflowTest { } @Test fun `login does nothing when name is empty`() { - val startState = WelcomeWorkflow.State("") + val startState = WelcomeWorkflow.State(TextController("")) val action = WelcomeWorkflow.onLogin() val (state, actionApplied) = action.applyTo(state = startState, props = Unit) // Since the name is empty, onLogin will not emit an output. assertNull(actionApplied.output) // The name is empty, as was specified in the initial state. - assertEquals("", state.name) + assertEquals("", state.name.textValue) } // endregion @@ -51,7 +42,7 @@ class WelcomeWorkflowTest { // Use the initial state provided by the welcome workflow. WelcomeWorkflow.testRender(props = Unit) .render { screen -> - assertEquals("", screen.username) + assertEquals("", screen.username.textValue) // Simulate tapping the log in button. No output will be emitted, as the name is empty. screen.onLoginTapped() @@ -61,25 +52,11 @@ class WelcomeWorkflowTest { } } - @Test fun `rendering name change`() { - // Use the initial state provided by the welcome workflow. - WelcomeWorkflow.testRender(props = Unit) - // Next, simulate the name updating, expecting the state to be changed to reflect the - // updated name. - .render { screen -> - screen.onUsernameChanged("Ada") - } - .verifyActionResult { state, _ -> - // https://github.com/square/workflow-kotlin/issues/230 - assertEquals("Ada", (state as WelcomeWorkflow.State).name) - } - } - @Test fun `rendering login`() { // Start with a name already entered. WelcomeWorkflow .testRender( - initialState = WelcomeWorkflow.State(name = "Ada"), + initialState = WelcomeWorkflow.State(name = TextController("Ada")), props = Unit ) // Simulate a log in button tap. diff --git a/samples/tutorial/tutorial-views/src/main/res/layout/todo_list_view.xml b/samples/tutorial/tutorial-views/src/main/res/layout/todo_list_view.xml index e26318102b..974e15c61e 100644 --- a/samples/tutorial/tutorial-views/src/main/res/layout/todo_list_view.xml +++ b/samples/tutorial/tutorial-views/src/main/res/layout/todo_list_view.xml @@ -1,9 +1,25 @@ - + > + +