diff --git a/samples/tutorial/tutorial-final/src/main/java/workflow/tutorial/RootWorkflow.kt b/samples/tutorial/tutorial-final/src/main/java/workflow/tutorial/RootNavigationWorkflow.kt similarity index 63% rename from samples/tutorial/tutorial-final/src/main/java/workflow/tutorial/RootWorkflow.kt rename to samples/tutorial/tutorial-final/src/main/java/workflow/tutorial/RootNavigationWorkflow.kt index db5b344b9..cb69d2ca0 100644 --- a/samples/tutorial/tutorial-final/src/main/java/workflow/tutorial/RootWorkflow.kt +++ b/samples/tutorial/tutorial-final/src/main/java/workflow/tutorial/RootNavigationWorkflow.kt @@ -7,14 +7,13 @@ import com.squareup.workflow1.renderChild import com.squareup.workflow1.ui.Screen import com.squareup.workflow1.ui.WorkflowUiExperimentalApi 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 +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>() { +object RootNavigationWorkflow : StatefulWorkflow>() { sealed class State { object Welcome : State() @@ -31,37 +30,29 @@ object RootWorkflow : StatefulWorkflow { - - // Our list of back stack items. Will always include the "WelcomeScreen". - val backstackScreens = mutableListOf() - + ): 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 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 index 46b8d40c6..02288ae93 100644 --- a/samples/tutorial/tutorial-final/src/main/java/workflow/tutorial/TodoEditRunner.kt +++ b/samples/tutorial/tutorial-final/src/main/java/workflow/tutorial/TodoEditRunner.kt @@ -16,8 +16,8 @@ class TodoEditRunner( rendering: TodoEditScreen, environment: ViewEnvironment ) { - binding.root.setBackHandler(rendering.onBackClick) - binding.save.setOnClickListener { rendering.onSaveClick() } + binding.root.setBackHandler(rendering.onBackPressed) + binding.save.setOnClickListener { rendering.onSavePressed() } 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 a4c892ee0..b2c88d7e8 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 @@ -13,8 +13,8 @@ data class TodoEditScreen( /** The contents, or "note" of the todo. */ val note: TextController, - val onBackClick: () -> Unit, - val onSaveClick: () -> Unit + val onBackPressed: () -> Unit, + val onSavePressed: () -> 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 a3d36015f..90bc6cc46 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 @@ -56,8 +56,8 @@ object TodoEditWorkflow : StatefulWorkflow, val onTodoSelected: (Int) -> Unit, - val onBackClick: () -> Unit, - val onAddClick: () -> Unit + val onBackPressed: () -> Unit, + val onAddPressed: () -> Unit ): AndroidScreen { override val viewFactory = ScreenViewFactory.fromViewBinding(TodoListViewBinding::inflate, ::TodoListScreenRunner) diff --git a/samples/tutorial/tutorial-final/src/main/java/workflow/tutorial/TodoListScreenRunner.kt b/samples/tutorial/tutorial-final/src/main/java/workflow/tutorial/TodoListScreenRunner.kt index ab7b330c1..791ace0e2 100644 --- a/samples/tutorial/tutorial-final/src/main/java/workflow/tutorial/TodoListScreenRunner.kt +++ b/samples/tutorial/tutorial-final/src/main/java/workflow/tutorial/TodoListScreenRunner.kt @@ -24,8 +24,8 @@ class TodoListScreenRunner( rendering: TodoListScreen, environment: ViewEnvironment ) { - todoListBinding.root.setBackHandler(rendering.onBackClick) - todoListBinding.add.setOnClickListener { rendering.onAddClick() } + todoListBinding.root.setBackHandler(rendering.onBackPressed) + todoListBinding.add.setOnClickListener { rendering.onAddPressed() } with(todoListBinding.todoListWelcome) { text = 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 af9eb23a4..2fbf44a7c 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 @@ -32,8 +32,8 @@ object TodoListWorkflow : StatelessWorkflow() username = renderProps.username, todoTitles = titles.map { it.textValue }, onTodoSelected = { context.actionSink.send(selectTodo(it)) }, - onBackClick = { context.actionSink.send(postGoBack) }, - onAddClick = { context.actionSink.send(postNewTodo) } + onBackPressed = { context.actionSink.send(postGoBack) }, + onAddPressed = { context.actionSink.send(postNewTodo) } ) } diff --git a/samples/tutorial/tutorial-final/src/main/java/workflow/tutorial/TodoWorkflow.kt b/samples/tutorial/tutorial-final/src/main/java/workflow/tutorial/TodoNavigationWorkflow.kt similarity index 87% rename from samples/tutorial/tutorial-final/src/main/java/workflow/tutorial/TodoWorkflow.kt rename to samples/tutorial/tutorial-final/src/main/java/workflow/tutorial/TodoNavigationWorkflow.kt index 7ae79932d..187b1f501 100644 --- a/samples/tutorial/tutorial-final/src/main/java/workflow/tutorial/TodoWorkflow.kt +++ b/samples/tutorial/tutorial-final/src/main/java/workflow/tutorial/TodoNavigationWorkflow.kt @@ -5,6 +5,7 @@ import com.squareup.workflow1.StatefulWorkflow import com.squareup.workflow1.action import com.squareup.workflow1.ui.Screen import com.squareup.workflow1.ui.WorkflowUiExperimentalApi +import com.squareup.workflow1.ui.container.BackStackScreen import workflow.tutorial.TodoEditWorkflow.EditProps import workflow.tutorial.TodoEditWorkflow.Output.Discard import workflow.tutorial.TodoEditWorkflow.Output.Save @@ -12,13 +13,13 @@ import workflow.tutorial.TodoListWorkflow.ListProps import workflow.tutorial.TodoListWorkflow.Output import workflow.tutorial.TodoListWorkflow.Output.NewTodo import workflow.tutorial.TodoListWorkflow.Output.SelectTodo -import workflow.tutorial.TodoWorkflow.Back -import workflow.tutorial.TodoWorkflow.State -import workflow.tutorial.TodoWorkflow.State.Step -import workflow.tutorial.TodoWorkflow.TodoProps +import workflow.tutorial.TodoNavigationWorkflow.Back +import workflow.tutorial.TodoNavigationWorkflow.State +import workflow.tutorial.TodoNavigationWorkflow.State.Step +import workflow.tutorial.TodoNavigationWorkflow.TodoProps @OptIn(WorkflowUiExperimentalApi::class) -object TodoWorkflow : StatefulWorkflow>() { +object TodoNavigationWorkflow : StatefulWorkflow>() { data class TodoProps(val name: String) @@ -58,7 +59,7 @@ object TodoWorkflow : StatefulWorkflow>() { renderProps: TodoProps, renderState: State, context: RenderContext - ): List { + ): BackStackScreen { val todoListScreen = context.renderChild( TodoListWorkflow, props = ListProps( @@ -75,7 +76,7 @@ object TodoWorkflow : StatefulWorkflow>() { return when (val step = renderState.step) { // On the "list" step, return just the list screen. - Step.List -> listOf(todoListScreen) + Step.List -> BackStackScreen(todoListScreen) is Step.Edit -> { // On the "edit" step, return both the list and edit screens. val todoEditScreen = context.renderChild( @@ -89,7 +90,7 @@ object TodoWorkflow : StatefulWorkflow>() { is Save -> saveChanges(output.todo, step.index) } } - return listOf(todoListScreen, todoEditScreen) + return BackStackScreen(todoListScreen, todoEditScreen) } } } 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 86071497f..0222d5cdf 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 @@ -32,7 +32,7 @@ class TutorialActivity : AppCompatActivity() { class TutorialViewModel(savedState: SavedStateHandle) : ViewModel() { val renderings: StateFlow by lazy { renderWorkflowIn( - workflow = RootWorkflow, + workflow = RootNavigationWorkflow, scope = viewModelScope, savedStateHandle = savedState ) 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 28f868dc5..42d759826 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 @@ -11,7 +11,7 @@ data class WelcomeScreen( /** The current name that has been entered. */ val username: TextController, /** Callback when the login button is tapped. */ - val onLoginTapped: () -> Unit + val onLogInPressed: () -> 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 index 92a969574..165e55677 100644 --- a/samples/tutorial/tutorial-final/src/main/java/workflow/tutorial/WelcomeScreenRunner.kt +++ b/samples/tutorial/tutorial-final/src/main/java/workflow/tutorial/WelcomeScreenRunner.kt @@ -18,6 +18,6 @@ class WelcomeScreenRunner( // 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() } + welcomeBinding.login.setOnClickListener { rendering.onLogInPressed() } } } 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 95feba066..5ab9907f1 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 @@ -28,13 +28,13 @@ object WelcomeWorkflow : StatefulWorkflow( context: RenderContext ): WelcomeScreen = WelcomeScreen( username = renderState.name, - onLoginTapped = { - // Whenever the login button is tapped, emit the onLogin action. - context.actionSink.send(onLogin()) + onLogInPressed = { + // Whenever the log in button is tapped, enqueue the logInAction. + context.actionSink.send(logInAction()) } ) - internal fun onLogin() = action { + internal fun logInAction() = action { // Don't log in if the name isn't filled in. 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/RootNavigationWorkflowTest.kt similarity index 91% rename from samples/tutorial/tutorial-final/src/test/java/workflow/tutorial/RootWorkflowTest.kt rename to samples/tutorial/tutorial-final/src/test/java/workflow/tutorial/RootNavigationWorkflowTest.kt index e83b8fb5b..02c2b885c 100644 --- a/samples/tutorial/tutorial-final/src/test/java/workflow/tutorial/RootWorkflowTest.kt +++ b/samples/tutorial/tutorial-final/src/test/java/workflow/tutorial/RootNavigationWorkflowTest.kt @@ -7,8 +7,8 @@ import com.squareup.workflow1.testing.testRender import com.squareup.workflow1.ui.TextController import com.squareup.workflow1.ui.WorkflowUiExperimentalApi import com.squareup.workflow1.ui.container.BackStackScreen -import workflow.tutorial.RootWorkflow.State.Todo -import workflow.tutorial.RootWorkflow.State.Welcome +import workflow.tutorial.RootNavigationWorkflow.State.Todo +import workflow.tutorial.RootNavigationWorkflow.State.Welcome import workflow.tutorial.WelcomeWorkflow.LoggedIn import kotlin.test.Test import kotlin.test.assertEquals @@ -16,12 +16,12 @@ import kotlin.test.assertNull import kotlin.test.assertTrue @OptIn(WorkflowUiExperimentalApi::class) -class RootWorkflowTest { +class RootNavigationWorkflowTest { // region Render @Test fun `welcome rendering`() { - RootWorkflow + RootNavigationWorkflow // Start in the Welcome state .testRender(initialState = Welcome, props = Unit) // The `WelcomeWorkflow` is expected to be started in this render. @@ -29,7 +29,7 @@ class RootWorkflowTest { workflowType = WelcomeWorkflow::class, rendering = WelcomeScreen( username = TextController("Ada"), - onLoginTapped = {} + onLogInPressed = {} ) ) // Now, validate that there is a single item in the BackStackScreen, which is our welcome @@ -48,7 +48,7 @@ class RootWorkflowTest { } @Test fun `login event`() { - RootWorkflow + RootNavigationWorkflow // Start in the Welcome state .testRender(initialState = Welcome, props = Unit) // The WelcomeWorkflow is expected to be started in this render. @@ -56,7 +56,7 @@ class RootWorkflowTest { workflowType = WelcomeWorkflow::class, rendering = WelcomeScreen( username = TextController("Ada"), - onLoginTapped = {} + onLogInPressed = {} ), // Simulate the WelcomeWorkflow sending an output of LoggedIn as if the "log in" button // was tapped. @@ -82,7 +82,7 @@ class RootWorkflowTest { // region Integration @Test fun `app flow`() { - RootWorkflow.launchForTestingFromStartWith { + RootNavigationWorkflow.launchForTestingFromStartWith { // First rendering is just the welcome screen. Update the name. awaitNextRendering().let { rendering -> assertEquals(1, rendering.frames.size) @@ -90,7 +90,7 @@ class RootWorkflowTest { // Enter a name and tap login welcomeScreen.username.textValue = "Ada" - welcomeScreen.onLoginTapped() + welcomeScreen.onLogInPressed() } // Expect the todo list to be rendered. Edit the first todo. @@ -113,7 +113,7 @@ class RootWorkflowTest { // Enter a title and save. editScreen.title.textValue = "New Title" - editScreen.onSaveClick() + editScreen.onSavePressed() } // Expect the todo list. Validate the title was updated. diff --git a/samples/tutorial/tutorial-final/src/test/java/workflow/tutorial/TodoWorkflowTest.kt b/samples/tutorial/tutorial-final/src/test/java/workflow/tutorial/TodoNavigationWorkflowTest.kt similarity index 88% rename from samples/tutorial/tutorial-final/src/test/java/workflow/tutorial/TodoWorkflowTest.kt rename to samples/tutorial/tutorial-final/src/test/java/workflow/tutorial/TodoNavigationWorkflowTest.kt index 9214615a4..231881c45 100644 --- a/samples/tutorial/tutorial-final/src/test/java/workflow/tutorial/TodoWorkflowTest.kt +++ b/samples/tutorial/tutorial-final/src/test/java/workflow/tutorial/TodoNavigationWorkflowTest.kt @@ -7,20 +7,20 @@ import com.squareup.workflow1.ui.TextController import com.squareup.workflow1.ui.WorkflowUiExperimentalApi import workflow.tutorial.TodoEditWorkflow.Output.Save import workflow.tutorial.TodoListWorkflow.Output.SelectTodo -import workflow.tutorial.TodoWorkflow.State -import workflow.tutorial.TodoWorkflow.State.Step.Edit -import workflow.tutorial.TodoWorkflow.State.Step.List -import workflow.tutorial.TodoWorkflow.TodoProps +import workflow.tutorial.TodoNavigationWorkflow.State +import workflow.tutorial.TodoNavigationWorkflow.State.Step.Edit +import workflow.tutorial.TodoNavigationWorkflow.State.Step.List +import workflow.tutorial.TodoNavigationWorkflow.TodoProps import kotlin.test.Test import kotlin.test.assertEquals @OptIn(WorkflowUiExperimentalApi::class) -class TodoWorkflowTest { +class TodoNavigationWorkflowTest { @Test fun `selecting todo`() { val todos = listOf(TodoModel(title = "Title", note = "Note")) - TodoWorkflow + TodoNavigationWorkflow .testRender( props = TodoProps(name = "Ada"), // Start from the list step to validate selecting a todo. @@ -36,8 +36,8 @@ class TodoWorkflowTest { username = "", todoTitles = listOf("Title"), onTodoSelected = {}, - onBackClick = {}, - onAddClick = {} + onBackPressed = {}, + onAddPressed = {} ), // Simulate selecting the first todo. output = WorkflowOutput(SelectTodo(index = 0)) @@ -62,7 +62,7 @@ class TodoWorkflowTest { @Test fun `saving todo`() { val todos = listOf(TodoModel(title = "Title", note = "Note")) - TodoWorkflow + TodoNavigationWorkflow .testRender( props = TodoProps(name = "Ada"), // Start from the edit step so we can simulate saving. @@ -78,8 +78,8 @@ class TodoWorkflowTest { username = "", todoTitles = listOf("Title"), onTodoSelected = {}, - onBackClick = {}, - onAddClick = {} + onBackPressed = {}, + onAddPressed = {} ) ) // Expect the TodoEditWorkflow to be rendered as well (as we're on the edit step). @@ -88,8 +88,8 @@ class TodoWorkflowTest { rendering = TodoEditScreen( title = TextController("Title"), note = TextController("Note"), - onBackClick = {}, - onSaveClick = {} + onBackPressed = {}, + onSavePressed = {} ), // Simulate it emitting an output of `.save` to update the state. output = WorkflowOutput( 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 99ebf9b37..e512b0134 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 @@ -16,7 +16,7 @@ class WelcomeWorkflowTest { @Test fun `login works`() { val startState = WelcomeWorkflow.State(TextController("myName")) - val action = WelcomeWorkflow.onLogin() + val action = WelcomeWorkflow.logInAction() val (_, actionApplied) = action.applyTo(state = startState, props = Unit) // Now a LoggedIn output should be emitted when the onLogin action was received. @@ -25,7 +25,7 @@ class WelcomeWorkflowTest { @Test fun `login does nothing when name is empty`() { val startState = WelcomeWorkflow.State(TextController("")) - val action = WelcomeWorkflow.onLogin() + val action = WelcomeWorkflow.logInAction() val (state, actionApplied) = action.applyTo(state = startState, props = Unit) // Since the name is empty, onLogin will not emit an output. @@ -45,7 +45,7 @@ class WelcomeWorkflowTest { assertEquals("", screen.username.textValue) // Simulate tapping the log in button. No output will be emitted, as the name is empty. - screen.onLoginTapped() + screen.onLogInPressed() } .verifyActionResult { _, output -> assertNull(output) @@ -61,7 +61,7 @@ class WelcomeWorkflowTest { ) // Simulate a log in button tap. .render { screen -> - screen.onLoginTapped() + screen.onLogInPressed() } // Finally, validate that LoggedIn was sent. .verifyActionResult { _, output -> diff --git a/workflow-ui/core-common/src/main/java/com/squareup/workflow1/ui/container/BackStackScreen.kt b/workflow-ui/core-common/src/main/java/com/squareup/workflow1/ui/container/BackStackScreen.kt index 7bd13ad70..b2f3ce7ff 100644 --- a/workflow-ui/core-common/src/main/java/com/squareup/workflow1/ui/container/BackStackScreen.kt +++ b/workflow-ui/core-common/src/main/java/com/squareup/workflow1/ui/container/BackStackScreen.kt @@ -115,6 +115,13 @@ public operator fun BackStackScreen.plus( return other?.let { BackStackScreen(frames + it.frames) } ?: this } +@WorkflowUiExperimentalApi +public operator fun BackStackScreen.plus( + other: BackStackScreen? +): BackStackScreen { + return other?.let { BackStackScreen(frames + it.frames) } ?: this +} + @WorkflowUiExperimentalApi public fun List.toBackStackScreenOrNull(): BackStackScreen? = fromListOrNull(this)