$30 off During Our Annual Pro Sale. View Details »

Unidirectional data flow on Android using Kotlin

Unidirectional data flow on Android using Kotlin

Have you ever heard about unidirectional data flow? Flux and/or Redux?

In this talk, we will talk about how to follow the principles that architectures like Flux and Redux do on the web, but on Android.

What is a state? A dispatcher? side effect? Controller view? How do we glue all these parts together? How to keep our domain isolated from the outer world so is easily testable?

We will cover all these topics and much more!!, and you know what? everything with a bit of Kotlin sauce, so we will see how we can take advantage of the cool stuff this language provide us to make our architecture even better.

KUnidirectional (sampe app github repo): https://github.com/CesarValiente/KUnidirectional
KUnidirectional videos (demos): https://goo.gl/H3z1w1

Cesar Valiente

May 02, 2017
Tweet

More Decks by Cesar Valiente

Other Decks in Programming

Transcript

  1. Unidirectional data flow
    on Android
    … using Kotlin
    @CesarValiente
    @CesarValiente

    View Slide

  2. Unidirectional?
    A
    B
    C
    D

    View Slide

  3. Unidirectional?
    A
    B
    C
    D

    View Slide

  4. Unidirectional?
    A
    B
    C
    D

    View Slide

  5. Unidirectional?
    A
    B
    C
    D

    View Slide

  6. Unidirectional?
    A
    B
    C
    D

    View Slide

  7. Global app state
    Current Value = 0
    Next Value = 0
    State is immutable.

    View Slide

  8. Global app state
    Current Value = 0
    Next Value = 0
    +1
    State is immutable.

    View Slide

  9. Global app state
    Current Value = 0
    +1
    Next Value = 1
    State is immutable.

    View Slide

  10. Global app state
    +1 State = 1
    Next Value = 1
    Current Value = 1
    State is immutable.

    View Slide

  11. Global app state
    Next Value = 1
    Current Value = 1
    State is immutable.

    View Slide

  12. Global app state
    Next Value = 1
    +1 Current Value = 1
    State is immutable.

    View Slide

  13. Global app state
    +1 Current Value = 1
    Next Value = 2
    State is immutable.

    View Slide

  14. Global app state
    +1
    Next Value = 2
    State = 2
    Current Value = 2
    State is immutable.

    View Slide

  15. Global app state
    Next Value = 2
    Current Value = 2
    State is immutable.

    View Slide

  16. Global app state
    Next Value = 2
    Current Value = 2
    +1
    State is immutable.

    View Slide

  17. Global app state
    Current Value = 2
    +1
    Next Value = 3
    State is immutable.

    View Slide

  18. Global app state
    +1
    Next Value = 3
    State = 3
    Current Value = 3
    State is immutable.

    View Slide

  19. Flux Redux

    View Slide

  20. Flux Redux
    STATE

    View Slide

  21. VIEW
    Flux Redux
    STATE

    View Slide

  22. VIEW
    ACTION
    Flux Redux
    STATE

    View Slide

  23. VIEW
    ACTION
    DISPATCHER
    Flux Redux
    STATE

    View Slide

  24. VIEW
    ACTION
    DISPATCHER
    REDUCER
    STORE
    Flux Redux
    STATE

    View Slide

  25. KUnidirectional
    https://github.com/CesarValiente/KUnidirectional

    View Slide

  26. KUnidirectional
    OSS sample app which shows this
    architecture.
    https://github.com/CesarValiente/KUnidirectional

    View Slide

  27. KUnidirectional
    OSS sample app which shows this
    architecture.
    A simple item list app.
    https://github.com/CesarValiente/KUnidirectional

    View Slide

  28. KUnidirectional
    OSS sample app which shows this
    architecture.
    A simple item list app.
    Everything is unidirectional.
    https://github.com/CesarValiente/KUnidirectional

    View Slide

  29. KUnidirectional
    OSS sample app which shows this
    architecture.
    A simple item list app.
    Everything is unidirectional.
    Without external libraries, just
    Kotlin + Android.
    https://github.com/CesarValiente/KUnidirectional

    View Slide

  30. ACTION
    REDUCER
    VIEW CONTROLLER-VIEW
    SIDE EFFECT 1
    STORE
    Action
    State
    User interaction
    UI Data
    Optional Action
    SIDE EFFECT N

    View Slide

  31. ACTION
    REDUCER
    VIEW CONTROLLER-VIEW
    SIDE EFFECT 1
    STORE
    Action
    State
    User interaction
    UI Data
    Optional Action
    SIDE EFFECT N

    View Slide

  32. ACTION
    REDUCER
    VIEW CONTROLLER-VIEW
    SIDE EFFECT 1
    STORE
    Action
    State
    User interaction
    UI Data
    Optional Action
    SIDE EFFECT N

    View Slide

  33. ACTION
    REDUCER
    VIEW CONTROLLER-VIEW
    SIDE EFFECT 1
    STORE
    Action
    State
    User interaction
    UI Data
    Optional Action
    SIDE EFFECT N

    View Slide

  34. ACTION
    REDUCER
    VIEW CONTROLLER-VIEW
    SIDE EFFECT 1
    STORE
    Action
    State
    User interaction
    UI Data
    Optional Action
    SIDE EFFECT N

    View Slide

  35. ACTION
    REDUCER
    VIEW CONTROLLER-VIEW
    SIDE EFFECT 1
    STORE
    Action
    State
    User interaction
    UI Data
    Optional Action
    SIDE EFFECT N

    View Slide

  36. ACTION
    REDUCER
    VIEW CONTROLLER-VIEW
    SIDE EFFECT 1
    STORE
    Action
    State
    User interaction
    UI Data
    Optional Action
    SIDE EFFECT N

    View Slide

  37. ACTION
    REDUCER
    VIEW CONTROLLER-VIEW
    SIDE EFFECT 1
    STORE
    Action
    State
    User interaction
    UI Data
    Optional Action
    SIDE EFFECT N

    View Slide

  38. Dependencies

    View Slide

  39. Dependencies
    VIEW
    ANDROID
    DEPENDENCIES

    View Slide

  40. Dependencies
    VIEW
    ANDROID
    DEPENDENCIES
    NO FRAMEWORK DEPENDENCIES
    ACTION
    REDUCER
    CONTROLLER-VIEW
    STORE

    View Slide

  41. Dependencies
    VIEW
    ANDROID
    DEPENDENCIES
    NO FRAMEWORK
    DEPENDENCIES
    SIDE EFFECT 1
    ANDROID
    DEPENDENCIES
    SIDE EFFECT N
    NO FRAMEWORK DEPENDENCIES
    ACTION
    REDUCER
    CONTROLLER-VIEW
    STORE

    View Slide

  42. VIEW
    View
    CONTROLLER-VIEW

    View Slide

  43. VIEW
    View
    CONTROLLER-VIEW
    ACTIVITY / FRAGMENT

    View Slide

  44. VIEW
    User interaction
    View
    CONTROLLER-VIEW
    ACTIVITY / FRAGMENT

    View Slide

  45. VIEW
    User interaction User interaction
    View
    CONTROLLER-VIEW
    ACTIVITY / FRAGMENT

    View Slide

  46. VIEW
    User interaction User interaction
    New UI data to render
    View
    CONTROLLER-VIEW
    ACTIVITY / FRAGMENT

    View Slide

  47. VIEW
    User interaction User interaction
    New UI data to render
    User result
    View
    CONTROLLER-VIEW
    ACTIVITY / FRAGMENT

    View Slide

  48. ControllerView
    CONTROLLER-VIEW
    Store

    View Slide

  49. ControllerView
    CONTROLLER-VIEW
    User interaction
    Store

    View Slide

  50. ControllerView
    CONTROLLER-VIEW
    User interaction
    ACTION
    create
    Store

    View Slide

  51. ControllerView
    CONTROLLER-VIEW
    User interaction
    ACTION
    create dispatch
    Store

    View Slide

  52. ControllerView
    CONTROLLER-VIEW
    User interaction
    ACTION
    create
    State
    dispatch
    Store

    View Slide

  53. ControllerView
    CONTROLLER-VIEW
    User interaction
    New UI data to render
    ACTION
    create
    State
    dispatch
    Store

    View Slide

  54. abstract class ControllerView(

    val store: Store,

    mainThread: ThreadExecutor? = null):
    LifecycleCallbacks, StateHandler(mainThread) {

    View Slide

  55. protected fun dispatch(action: T) {

    store.dispatch(action)

    }
    abstract class ControllerView(

    val store: Store,

    mainThread: ThreadExecutor? = null):
    LifecycleCallbacks, StateHandler(mainThread) {

    View Slide

  56. protected fun dispatch(action: T) {

    store.dispatch(action)

    }
    abstract class ControllerView(

    val store: Store,

    mainThread: ThreadExecutor? = null):
    LifecycleCallbacks, StateHandler(mainThread) {
    abstract fun handleState(state: State)

    }

    View Slide

  57. Action
    ACTION

    View Slide

  58. Action
    ACTION
    CREATE DELETE
    FETCH
    UPDATE

    View Slide

  59. Action
    ACTION
    CREATE DELETE
    FETCH
    UPDATE
    NAVIGATE OTHERS

    View Slide

  60. sealed class Action

    View Slide

  61. sealed class Action
    sealed class UpdateAction : Action() {

    data class ReorderItemsAction(val items: List) : UpdateAction()


    data class UpdateItemAction(val localId: String,

    val text: String?,

    val color: Color) : UpdateAction()


    data class UpdateFavoriteAction(
    val localId: String, val favorite: Boolean) : UpdateAction()


    data class UpdateColorAction(
    val localId: String, val color: Color) : UpdateAction()

    }

    View Slide

  62. sealed class Action
    sealed class UpdateAction : Action() {

    data class ReorderItemsAction(val items: List) : UpdateAction()


    data class UpdateItemAction(val localId: String,

    val text: String?,

    val color: Color) : UpdateAction()


    data class UpdateFavoriteAction(
    val localId: String, val favorite: Boolean) : UpdateAction()


    data class UpdateColorAction(
    val localId: String, val color: Color) : UpdateAction()

    }
    sealed class ReadAction : Action() {

    class FetchItemsAction : ReadAction()

    data class ItemsLoadedAction(val items: List) : ReadAction()

    }

    View Slide

  63. STATE
    Reducer
    Store
    Store
    Remember, State is immutable.

    View Slide

  64. STATE
    Reducer
    ACTION
    Store
    Action
    Store
    Remember, State is immutable.

    View Slide

  65. STATE
    Reducer
    ACTION
    NEW STATE
    Store
    Action
    Store
    Remember, State is immutable.

    View Slide

  66. STATE
    Reducer
    ACTION
    NEW STATE
    Store
    New state Action
    Action Action
    Store
    Remember, State is immutable.

    View Slide

  67. State
    enum class Navigation {

    ITEMS_LIST,

    EDIT_ITEM

    }

    data class ItemsListScreen(

    val items: List = emptyList())


    data class EditItemScreen(val currentItem: Item = Item())


    data class State(

    val itemsListScreen: ItemsListScreen = ItemsListScreen(),

    val editItemScreen: EditItemScreen = EditItemScreen(),

    val navigation: Navigation = Navigation.ITEMS_LIST)
    Remember, State is immutable.

    View Slide

  68. Store
    abstract class Store(
    val sideEffects: CopyOnWriteArrayList = CopyOnWriteArrayList(),

    val stateHandlers: CopyOnWriteArrayList = CopyOnWriteArrayList(),

    val storeThread: ThreadExecutor? = null) : Subscribers {

    View Slide

  69. Store
    abstract class Store(
    val sideEffects: CopyOnWriteArrayList = CopyOnWriteArrayList(),

    val stateHandlers: CopyOnWriteArrayList = CopyOnWriteArrayList(),

    val storeThread: ThreadExecutor? = null) : Subscribers {
    private fun handle(action: Action) {

    val newState = reduce(action, state)

    dispatch(newState)

    sideEffects.dispatch(action)

    }

    View Slide

  70. Store
    abstract class Store(
    val sideEffects: CopyOnWriteArrayList = CopyOnWriteArrayList(),

    val stateHandlers: CopyOnWriteArrayList = CopyOnWriteArrayList(),

    val storeThread: ThreadExecutor? = null) : Subscribers {
    private fun handle(action: Action) {

    val newState = reduce(action, state)

    dispatch(newState)

    sideEffects.dispatch(action)

    }
    fun dispatch(newState: State) {

    state = newState

    stateHandlers.dispatch(state)

    }

    . . .

    View Slide

  71. Reducer
    . . .
    private fun reduce(action: Action, currentState: State): State =

    when (action) {

    is CreationAction -> CreationReducer.reduce(action, currentState)

    is UpdateAction -> UpdateReducer.reduce(action, currentState)

    is ReadAction -> ReadReducer.reduce(action, currentState)

    is DeleteAction -> DeleteReducer.reduce(action, currentState)

    is NavigationAction -> NavigationReducer.reduce(action, currentState)

    }
    }

    View Slide

  72. abstract class Reducer {


    open fun reduce(action: T, currentState: State) : State =

    with(currentState) {

    currentState.copy(

    itemsListScreen = reduceItemsListScreen(action, itemsListScreen),

    editItemScreen = reduceEditItemScreen(action, editItemScreen),

    navigation = reduceNavigation(action, navigation)

    )

    }
    . . .
    }
    Reducer

    View Slide

  73. Models

    View Slide

  74. STORE
    STORE
    MODELS
    Models

    View Slide

  75. STORE
    STORE
    MODELS
    PRESENTATION
    STORE MODELS +
    EXTENSIONS
    Models

    View Slide

  76. STORE
    STORE
    MODELS
    PRESENTATION
    STORE MODELS +
    EXTENSIONS
    Models
    PERSISTENCE
    PERSISTENCE
    MODELS
    MAPPER

    View Slide

  77. enum class Color {

    RED, YELLOW, GREEN, BLUE, WHITE

    }


    data class Item(

    val localId: String = generateLocalId(),

    val text: String? = null,

    val favorite: Boolean = false,

    val color: Color = Color.WHITE,

    val position: Long = object : PositionsFactory {}.newPosition())
    Store Models

    View Slide

  78. import com.cesarvaliente.kunidirectional.persistence.Color as PersistenceColor

    import com.cesarvaliente.kunidirectional.persistence.Item as PersistenceItem

    import com.cesarvaliente.kunidirectional.store.Color as StoreColor

    import com.cesarvaliente.kunidirectional.store.Item as StoreItem
    Mapper
    fun StoreItem.toPersistenceItem(): PersistenceItem =

    with(this) {

    PersistenceItem(localId, text, favorite,
    color.toPersistenceColor(), position)

    }


    fun StoreColor.toPersistenceColor(): PersistenceColor =

    when (this) {

    StoreColor.BLUE -> PersistenceColor.BLUE

    StoreColor.GREEN -> PersistenceColor.GREEN

    StoreColor.RED -> PersistenceColor.RED

    StoreColor.WHITE -> PersistenceColor.WHITE

    StoreColor.YELLOW -> PersistenceColor.YELLOW

    }

    View Slide

  79. Presentation models?
    fun Color.toColorResource(): Int =

    when (this) {

    RED -> R.color.red

    YELLOW -> R.color.yellow

    GREEN -> R.color.green

    BLUE -> R.color.blue

    WHITE -> R.color.white

    }


    fun Item.getStableId(): Long = this.localId.hashCode().toLong()

    View Slide

  80. REDUCER
    Side Effect

    View Slide

  81. SIDE EFFECT 1
    REDUCER
    Side Effect

    View Slide

  82. SIDE EFFECT 1 SIDE EFFECT 2
    REDUCER
    Side Effect

    View Slide

  83. SIDE EFFECT N
    SIDE EFFECT 1 SIDE EFFECT 2
    REDUCER
    . . .
    Side Effect

    View Slide

  84. SIDE EFFECT N
    SIDE EFFECT 1 SIDE EFFECT 2
    REDUCER
    . . .
    Side Effect
    Action

    View Slide

  85. SIDE EFFECT N
    SIDE EFFECT 1 SIDE EFFECT 2
    REDUCER
    . . .
    Side Effect
    Action
    New action

    View Slide

  86. Threading?
    VIEW STORE PERSISTENCE
    SIDE EFFECT

    View Slide

  87. Threading?
    VIEW STORE PERSISTENCE
    SIDE EFFECT
    UI Thread

    View Slide

  88. Threading?
    VIEW STORE PERSISTENCE
    SIDE EFFECT
    UI Thread Store Thread

    View Slide

  89. Threading?
    VIEW STORE PERSISTENCE
    SIDE EFFECT
    UI Thread Store Thread
    Persistence
    Thread
    (Side Effect N Thread)

    View Slide

  90. Testing?

    View Slide

  91. Testing?
    UI tests on Presentation layer (View).

    View Slide

  92. Testing?
    UI tests on Presentation layer (View).
    Unit + Integration tests on our ControllerViews.

    View Slide

  93. Testing?
    UI tests on Presentation layer (View).
    Unit + Integration tests on our ControllerViews.
    Unit + instrumentation tests in our Persistence layer (side effect).

    View Slide

  94. Testing?
    UI tests on Presentation layer (View).
    Unit + Integration tests on our ControllerViews.
    Unit + instrumentation tests in our Persistence layer (side effect).
    Unit tests in our Store layer.

    View Slide

  95. Demo 1!

    View Slide

  96. Demo 1!

    View Slide

  97. Demo 1!
    FETCH LIST
    ITEMS
    LIST ITEMS
    Action
    State

    View Slide

  98. Demo 1!
    OPEN EDIT ITEM NAVIGATION UPDATED
    FETCH LIST
    ITEMS
    LIST ITEMS
    Action
    State

    View Slide

  99. Demo 1!
    OPEN EDIT ITEM NAVIGATION UPDATED
    SAVE ITEM NEW ITEM
    FETCH LIST
    ITEMS
    LIST ITEMS
    Action
    State

    View Slide

  100. Demo 1!
    OPEN EDIT ITEM NAVIGATION UPDATED
    SAVE ITEM NEW ITEM
    TO NOTE LIST NAVIGATION UPDATED
    FETCH LIST
    ITEMS
    LIST ITEMS
    Action
    State

    View Slide

  101. Demo 1!
    OPEN EDIT ITEM NAVIGATION UPDATED
    SAVE ITEM NEW ITEM
    TO NOTE LIST NAVIGATION UPDATED
    FETCH LIST
    ITEMS
    LIST ITEMS
    FETCH LIST
    ITEMS
    LIST ITEMS
    Action
    State

    View Slide

  102. Demo 2!
    Side effects are optional and
    decoupled elements.

    View Slide

  103. Demo 2!
    Side effects are optional and
    decoupled elements.

    View Slide

  104. Some thoughts

    View Slide

  105. Some thoughts
    This is not the Philosopher Stone of the architectures.

    View Slide

  106. Some thoughts
    Unidirectional makes your code easy to follow.
    This is not the Philosopher Stone of the architectures.

    View Slide

  107. Some thoughts
    Decoupling elements in your app makes maintainability and testing easier.
    Unidirectional makes your code easy to follow.
    This is not the Philosopher Stone of the architectures.

    View Slide

  108. Some thoughts
    Decoupling elements in your app makes maintainability and testing easier.
    Unidirectional makes your code easy to follow.
    This is not the Philosopher Stone of the architectures.
    Immutability makes your code safer.

    View Slide

  109. Some thoughts
    Decoupling elements in your app makes maintainability and testing easier.
    Take advantage of the tools you have.
    Unidirectional makes your code easy to follow.
    This is not the Philosopher Stone of the architectures.
    Immutability makes your code safer.

    View Slide

  110. Some thoughts
    Decoupling elements in your app makes maintainability and testing easier.
    Take advantage of the tools you have.
    What is good for our problem maybe is not good for yours.
    Unidirectional makes your code easy to follow.
    This is not the Philosopher Stone of the architectures.
    Immutability makes your code safer.

    View Slide

  111. ?
    @CesarValiente
    @CesarValiente

    View Slide

  112. Thanks!
    @CesarValiente
    @CesarValiente

    View Slide

  113. Useful Links
    KUnidirectional app
    KUnidirectional demo videos
    Flux
    Redux
    Luis G. Valle: Flux on Android
    André Staltz: Unidirectional data flow architectures
    Austin Mueller: Flux and Android
    Brian Egan and Guillaume Lung: Exploring the possibilities of Unidirectional Data Flow
    Architectures on Android

    View Slide

  114. License
    (cc) 2017 César Valiente.
    Some rights reserved.
    This document is distributed under the Creative
    Commons Attribution-ShareAlike 3.0 license, available in
    http://creativecommons.org/licenses/by-sa/3.0/

    View Slide

  115. Image licenses
    Flux and Redux images are property of Facebook.
    Emojis by Emoji One (CC-BY): http://emojione.com/

    View Slide