Slide 1

Slide 1 text

Mohit Sarveiya Using Square Workflow for Android & iOS @heyitsmohit

Slide 2

Slide 2 text

Using Square Workflow for Android & iOS ● Purpose ● Building screens & setting up state ● Working with Compose

Slide 3

Slide 3 text

Purpose ● Clear boundaries between components ● Immutable State ● Separation between state & UI

Slide 4

Slide 4 text

Workflow

Slide 5

Slide 5 text

Workflow 
 (State)

Slide 6

Slide 6 text

Workflow 
 (State) Actions

Slide 7

Slide 7 text

Workflow 
 (State) Actions

Slide 8

Slide 8 text

Workflow 
 (State) Rendering

Slide 9

Slide 9 text

Action UI

Slide 10

Slide 10 text

Action UI State

Slide 11

Slide 11 text

Action UI State Rendering

Slide 12

Slide 12 text

Action UI State Rendering

Slide 13

Slide 13 text

Creating Workflows ● State ● Actions ● Screens

Slide 14

Slide 14 text

Square Workflow with Android Screens, Workflows & Renderings

Slide 15

Slide 15 text

9:41 Login Username Password

Slide 16

Slide 16 text

Workflow Layout Runner Screen

Slide 17

Slide 17 text

Setup ● AndroidX View Model ● View Binding

Slide 18

Slide 18 text

Screen ● Represents view model for screen

Slide 19

Slide 19 text

Screen data class LoginScreen( val username: String, Val password: String, val onLoginClicked: () -> Unit )

Slide 20

Slide 20 text

Layout Runner ● Specifies how to update views ● Supports view binding

Slide 21

Slide 21 text

Layout Runner class LoginLayoutRunner( val loginViewBinding: LoginViewBinding ) : LayoutRunner { }

Slide 22

Slide 22 text

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

Slide 23

Slide 23 text

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

Slide 24

Slide 24 text

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

Slide 25

Slide 25 text

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

Slide 26

Slide 26 text

Workflow Layout Runner Screen

Slide 27

Slide 27 text

Workflow 
 (State) Rendering Actions

Slide 28

Slide 28 text

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

Slide 29

Slide 29 text

fun onUsernameChanged(username: String) = action { state = state.copy(username = username) } Action

Slide 30

Slide 30 text

object LoginWorkflow : StatefulWorkflow { } Workflow

Slide 31

Slide 31 text

object LoginWorkflow : StatefulWorkflow { override fun initialState() { } override fun render() { } } Workflow

Slide 32

Slide 32 text

object LoginWorkflow : StatefulWorkflow { override fun initialState() { } override fun render() { } } Workflow

Slide 33

Slide 33 text

fun render(renderState: State) { } Workflow

Slide 34

Slide 34 text

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

Slide 35

Slide 35 text

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

Slide 36

Slide 36 text

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

Slide 37

Slide 37 text

fun onUsernameChanged(username: String) = action { state = state.copy(username = username) } Action

Slide 38

Slide 38 text

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

Slide 39

Slide 39 text

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

Slide 40

Slide 40 text

fun initialState(): State { State( username = "" ) } Workflow

Slide 41

Slide 41 text

Workflow 
 (State) Rendering Actions

Slide 42

Slide 42 text

View & View Model View View Model Workflow

Slide 43

Slide 43 text

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

Slide 44

Slide 44 text

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

Slide 45

Slide 45 text

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

Slide 46

Slide 46 text

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

Slide 47

Slide 47 text

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

Slide 48

Slide 48 text

Workflow Layout Runner Screen

Slide 49

Slide 49 text

9:41 Login Username Password

Slide 50

Slide 50 text

Action UI State Rendering

Slide 51

Slide 51 text

Navigation & Props

Slide 52

Slide 52 text

Workflow B Workflow A Props

Slide 53

Slide 53 text

9:41 Login Username Password

Slide 54

Slide 54 text

9:41 Login Username Password 9:41 Username

Slide 55

Slide 55 text

Todo Workflow Login Workflow Root Workflow

Slide 56

Slide 56 text

Root Workflow ● Navigation States ● Backstack

Slide 57

Slide 57 text

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

Slide 58

Slide 58 text

sealed class State { object Login : State() data class Todo(val username: String) : State() } State

Slide 59

Slide 59 text

Root Workflow object RootWorkflow : StatefulWorkflow { }

Slide 60

Slide 60 text

Root Workflow object RootWorkflow : StatefulWorkflow { fun render( renderProps: Unit, renderState: State, context: RenderContext ): BackStackScreen }

Slide 61

Slide 61 text

Root Workflow fun render( ... ): BackStackScreen { 
 val backstackScreens = mutableListOf() }

Slide 62

Slide 62 text

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

Slide 63

Slide 63 text

Root Workflow fun render( ... ): BackStackScreen { 
 val loginScreen = context.renderChild(LoginWorkflow) backstackScreens += loginScreen }

Slide 64

Slide 64 text

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

Slide 65

Slide 65 text

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

Slide 66

Slide 66 text

Todo Workflow Login Workflow Root Workflow

Slide 67

Slide 67 text

Todo List Screen data class TodoListScreen( val username: String, val todoTitles: List )

Slide 68

Slide 68 text

Todo Workflow State data class State( val todos: List )

Slide 69

Slide 69 text

Todo Workflow State fun initialState() = State( listOf( TodoModel( title = "Workout", note = "Workout" ) ) )

Slide 70

Slide 70 text

Todo Workflow Login Workflow Props Username

Slide 71

Slide 71 text

Props object TodoListWorkflow : StatefulWorkflow() { data class ListProps(val username: String) }

Slide 72

Slide 72 text

Todo Workflow Login Workflow Root Workflow

Slide 73

Slide 73 text

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

Slide 74

Slide 74 text

Root Workflow fun render( ... ): BackStackScreen { when (renderState) { is Todo -> { val todoScreen = context.renderChild( TodoWorkflow, ListProps(username = renderState.username) ) } } }

Slide 75

Slide 75 text

View Model View

Slide 76

Slide 76 text

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

Slide 77

Slide 77 text

View Model class LoginViewModel : ViewModel() { val renderings: StateFlow by lazy { renderWorkflowIn( workflow = RootWorkflow, scope = viewModelScope, savedStateHandle = savedState ) } }

Slide 78

Slide 78 text

Todo Layout Runner Login Layout Runner

Slide 79

Slide 79 text

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

Slide 80

Slide 80 text

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

Slide 81

Slide 81 text

Todo Workflow Login Workflow Root Workflow

Slide 82

Slide 82 text

9:41 Login Username Password 9:41 Username

Slide 83

Slide 83 text

Square Workflow with Jetpack Compose

Slide 84

Slide 84 text

9:41 Hello 9:41 Goodbye

Slide 85

Slide 85 text

Workflow Binding

Slide 86

Slide 86 text

Rendering data class Rendering( val message: String, val onClick: () -> Unit )

Slide 87

Slide 87 text

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

Slide 88

Slide 88 text

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

Slide 89

Slide 89 text

State enum class State { Hello, Goodbye; fun theOtherState(): State = when (this) { Hello -> Goodbye Goodbye -> Hello } }

Slide 90

Slide 90 text

Action val helloAction = action { state = state.theOtherState() }

Slide 91

Slide 91 text

Workflow object HelloWorkflow : StatefulWorkflow { }

Slide 92

Slide 92 text

Workflow object HelloWorkflow : StatefulWorkflow { 
 override fun render(): Rendering = Rendering( message = renderState.name, onClick = { context.actionSink.send(helloAction) } ) 
 }

Slide 93

Slide 93 text

Action UI State Rendering

Slide 94

Slide 94 text

9:41 Hello 9:41 Goodbye

Slide 95

Slide 95 text

No content

Slide 96

Slide 96 text

No content

Slide 97

Slide 97 text

Thank You! www.codingwithmohit.com @heyitsmohit