Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Using Square Workflow for Android & iOS

Using Square Workflow for Android & iOS

B3f560d34c14a9113e5024bc34ac26a0?s=128

Mohit S

May 18, 2022
Tweet

More Decks by Mohit S

Other Decks in Programming

Transcript

  1. Mohit Sarveiya Using Square Workflow for Android & iOS @heyitsmohit

  2. Using Square Workflow for Android & iOS • Purpose •

    Building screens & setting up state • Working with Compose
  3. Purpose • Clear boundaries between components • Immutable State •

    Separation between state & UI
  4. Workflow

  5. Workflow 
 (State)

  6. Workflow 
 (State) Actions

  7. Workflow 
 (State) Actions

  8. Workflow 
 (State) Rendering

  9. Action UI

  10. Action UI State

  11. Action UI State Rendering

  12. Action UI State Rendering

  13. Creating Workflows • State • Actions • Screens

  14. Square Workflow with Android Screens, Workflows & Renderings

  15. 9:41 Login Username Password

  16. Workflow Layout Runner Screen

  17. Setup • AndroidX View Model • View Binding

  18. Screen • Represents view model for screen

  19. Screen data class LoginScreen( val username: String, Val password: String,

    val onLoginClicked: () -> Unit )
  20. Layout Runner • Specifies how to update views • Supports

    view binding
  21. Layout Runner class LoginLayoutRunner( val loginViewBinding: LoginViewBinding ) : LayoutRunner<LoginScreen>

    { }
  22. Layout Runner override fun showRendering(rendering: LoginScreen) { loginViewBinding.username.text = rendering.username

    loginViewBinding.username.setTextChangedListener { rendering.onUsernameChanged(it.toString()) } loginViewBinding.login.setOnClickListener { rendering.onLoginClicked() } }
  23. Layout Runner override fun showRendering(rendering: LoginScreen) { loginViewBinding.username.text = rendering.username

    loginViewBinding.username.setTextChangedListener { rendering.onUsernameChanged(it.toString()) } loginViewBinding.login.setOnClickListener { rendering.onLoginClicked() } }
  24. Layout Runner override fun showRendering(rendering: LoginScreen) { loginViewBinding.username.text = rendering.username

    loginViewBinding.username.setTextChangedListener { rendering.onUsernameChanged(it.toString()) } loginViewBinding.login.setOnClickListener { rendering.onLoginClicked() } }
  25. Layout Runner override fun showRendering(rendering: LoginScreen) { loginViewBinding.username.text = rendering.username

    loginViewBinding.username.setTextChangedListener { rendering.onUsernameChanged(it.toString()) } loginViewBinding.login.setOnClickListener { rendering.onLoginClicked() } }
  26. Workflow Layout Runner Screen

  27. Workflow 
 (State) Rendering Actions

  28. data class State( val username: String, val password: String )

    State
  29. fun onUsernameChanged(username: String) = action { state = state.copy(username =

    username) } Action
  30. object LoginWorkflow : StatefulWorkflow<State, LoginScreen> { } Workflow

  31. object LoginWorkflow : StatefulWorkflow<State, LoginScreen> { override fun initialState() {

    } override fun render() { } } Workflow
  32. object LoginWorkflow : StatefulWorkflow<State, LoginScreen> { override fun initialState() {

    } override fun render() { } } Workflow
  33. fun render(renderState: State) { } Workflow

  34. fun render(renderState: State): LoginScreen { } Workflow

  35. fun render(renderState: State): LoginScreen { LoginScreen( username = renderState.username, onUsernameChanged

    = { ... }, onLoginCliked = {} ) } Workflow
  36. fun render(renderState: State): LoginScreen { LoginScreen( username = renderState.username, onUsernameChanged

    = { ... }, onLoginCliked = {} ) } Workflow
  37. fun onUsernameChanged(username: String) = action { state = state.copy(username =

    username) } Action
  38. fun render(renderState: State): LoginScreen { LoginScreen( username = renderState.username, onUsernameChanged

    = { context.actionSink.send() }, onLoginCliked = {} ) } Workflow
  39. fun render(renderState: State): LoginScreen { LoginScreen( username = renderState.username, onUsernameChanged

    = { context.actionSink.send(onUserChanged(it)) }, onLoginCliked = {} ) } Workflow
  40. fun initialState(): State { State( username = "" ) }

    Workflow
  41. Workflow 
 (State) Rendering Actions

  42. View & View Model View View Model Workflow

  43. View Model class LoginViewModel : ViewModel() { val renderings: StateFlow<LoginScreen>

    by lazy { renderWorkflowIn( workflow = LoginWorkflow, scope = viewModelScope, savedStateHandle = savedState ) } }
  44. View Model class LoginViewModel : ViewModel() { val renderings: StateFlow<LoginScreen>

    by lazy { renderWorkflowIn( workflow = LoginWorkflow, scope = viewModelScope, savedStateHandle = savedState ) } }
  45. View class LoginActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?)

    { super.onCreate(savedInstanceState) 
 
 val model: LoginViewModel by viewModels() setContentView( WorkflowLayout(this).apply { start(model.renderings) } ) } }
  46. View class LoginActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?)

    { super.onCreate(savedInstanceState) 
 
 val model: LoginViewModel by viewModels() setContentView( WorkflowLayout(this).apply { start(model.renderings) } ) } }
  47. View class LoginActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?)

    { super.onCreate(savedInstanceState) 
 setContentView( WorkflowLayout(this).apply { start(model.renderings) } ) } }
  48. Workflow Layout Runner Screen

  49. 9:41 Login Username Password

  50. Action UI State Rendering

  51. Navigation & Props

  52. Workflow B Workflow A Props

  53. 9:41 Login Username Password

  54. 9:41 Login Username Password 9:41 Username

  55. Todo Workflow Login Workflow Root Workflow

  56. Root Workflow • Navigation States • Backstack

  57. sealed class State { object Login : State() } State

  58. sealed class State { object Login : State() data class

    Todo(val username: String) : State() } State
  59. Root Workflow object RootWorkflow : StatefulWorkflow { }

  60. Root Workflow object RootWorkflow : StatefulWorkflow { fun render( renderProps:

    Unit, renderState: State, context: RenderContext ): BackStackScreen<Any> }
  61. Root Workflow fun render( ... ): BackStackScreen<Any> { 
 val

    backstackScreens = mutableListOf<Any>() }
  62. Root Workflow fun render( ... ): BackStackScreen<Any> { 
 val

    loginScreen = context.renderChild(LoginWorkflow) }
  63. Root Workflow fun render( ... ): BackStackScreen<Any> { 
 val

    loginScreen = context.renderChild(LoginWorkflow) backstackScreens += loginScreen }
  64. Root Workflow fun render( ... ): BackStackScreen<Any> { 
 when

    (renderState) { is Todo -> { } } }
  65. Root Workflow fun render( ... ): BackStackScreen<Any> { 
 when

    (renderState) { is Todo -> { val todoScreen = context.renderChild(TodoWorkflow) backstackScreens += todoScreen } } }
  66. Todo Workflow Login Workflow Root Workflow

  67. Todo List Screen data class TodoListScreen( val username: String, val

    todoTitles: List<String> )
  68. Todo Workflow State data class State( val todos: List<TodoModel> )

  69. Todo Workflow State fun initialState() = State( listOf( TodoModel( title

    = "Workout", note = "Workout" ) ) )
  70. Todo Workflow Login Workflow Props Username

  71. Props object TodoListWorkflow : StatefulWorkflow() { data class ListProps(val username:

    String) }
  72. Todo Workflow Login Workflow Root Workflow

  73. Root Workflow fun render( ... ): BackStackScreen<Any> { when (renderState)

    { is Todo -> { val todoScreen = context.renderChild(TodoWorkflow) backstackScreens += todoScreen } } }
  74. Root Workflow fun render( ... ): BackStackScreen<Any> { when (renderState)

    { is Todo -> { val todoScreen = context.renderChild( TodoWorkflow, ListProps(username = renderState.username) ) } } }
  75. View Model View

  76. View Model class LoginViewModel : ViewModel() { val renderings: StateFlow<LoginScreen>

    by lazy { renderWorkflowIn( workflow = LoginWorkflow, scope = viewModelScope, savedStateHandle = savedState ) } }
  77. View Model class LoginViewModel : ViewModel() { val renderings: StateFlow<LoginScreen>

    by lazy { renderWorkflowIn( workflow = RootWorkflow, scope = viewModelScope, savedStateHandle = savedState ) } }
  78. Todo Layout Runner Login Layout Runner

  79. View Registry val viewRegistry = ViewRegistry( BackStackContainer, LoginLayoutRunner, TodoListLayoutRunner )

  80. View Registry setContentView( WorkflowLayout(this).apply { start(model.renderings, viewRegistry) } )

  81. Todo Workflow Login Workflow Root Workflow

  82. 9:41 Login Username Password 9:41 Username

  83. Square Workflow with Jetpack Compose

  84. 9:41 Hello 9:41 Goodbye

  85. Workflow Binding

  86. Rendering data class Rendering( val message: String, val onClick: ()

    -> Unit )
  87. Binding composeScreenViewFactory<HelloWorkflow.Rendering> { rendering, _ -> Text( rendering.message, modifier =

    Modifier .clickable(onClick = rendering.onClick) .fillMaxSize() .wrapContentSize(Alignment.Center) ) }
  88. Binding composeScreenViewFactory<HelloWorkflow.Rendering> { rendering, _ -> Text( rendering.message, modifier =

    Modifier .clickable(onClick = rendering.onClick) .fillMaxSize() .wrapContentSize(Alignment.Center) ) }
  89. State enum class State { Hello, Goodbye; fun theOtherState(): State

    = when (this) { Hello -> Goodbye Goodbye -> Hello } }
  90. Action val helloAction = action { state = state.theOtherState() }

  91. Workflow object HelloWorkflow : StatefulWorkflow { }

  92. Workflow object HelloWorkflow : StatefulWorkflow { 
 override fun render():

    Rendering = Rendering( message = renderState.name, onClick = { context.actionSink.send(helloAction) } ) 
 }
  93. Action UI State Rendering

  94. 9:41 Hello 9:41 Goodbye

  95. None
  96. None
  97. Thank You! www.codingwithmohit.com @heyitsmohit