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

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

9fa66713006f997a5c658adabd2add25?s=128

Cesar Valiente

May 02, 2017
Tweet

Transcript

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

  2. Unidirectional? A B C D

  3. Unidirectional? A B C D

  4. Unidirectional? A B C D

  5. Unidirectional? A B C D

  6. Unidirectional? A B C D

  7. Global app state Current Value = 0 Next Value =

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

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

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

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

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

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

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

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

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

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

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

    3 Current Value = 3 State is immutable.
  19. Flux Redux

  20. Flux Redux STATE

  21. VIEW Flux Redux STATE

  22. VIEW ACTION Flux Redux STATE

  23. VIEW ACTION DISPATCHER Flux Redux STATE

  24. VIEW ACTION DISPATCHER REDUCER STORE Flux Redux STATE

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

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

  27. KUnidirectional OSS sample app which shows this architecture. A simple

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

    item list app. Everything is unidirectional. https://github.com/CesarValiente/KUnidirectional
  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
  30. ACTION REDUCER VIEW CONTROLLER-VIEW SIDE EFFECT 1 STORE Action State

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

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

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

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

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

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

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

    User interaction UI Data Optional Action SIDE EFFECT N
  38. Dependencies

  39. Dependencies VIEW ANDROID DEPENDENCIES

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

    STORE
  41. Dependencies VIEW ANDROID DEPENDENCIES NO FRAMEWORK DEPENDENCIES SIDE EFFECT 1

    ANDROID DEPENDENCIES SIDE EFFECT N NO FRAMEWORK DEPENDENCIES ACTION REDUCER CONTROLLER-VIEW STORE
  42. VIEW View CONTROLLER-VIEW

  43. VIEW View CONTROLLER-VIEW ACTIVITY / FRAGMENT

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

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

  46. VIEW User interaction User interaction New UI data to render

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

    User result View CONTROLLER-VIEW ACTIVITY / FRAGMENT
  48. ControllerView CONTROLLER-VIEW Store

  49. ControllerView CONTROLLER-VIEW User interaction Store

  50. ControllerView CONTROLLER-VIEW User interaction ACTION create Store

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

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

  53. ControllerView CONTROLLER-VIEW User interaction New UI data to render ACTION

    create State dispatch Store
  54. abstract class ControllerView(
 val store: Store,
 mainThread: ThreadExecutor? = null):

    LifecycleCallbacks, StateHandler(mainThread) {
  55. protected fun <T : Action> dispatch(action: T) {
 store.dispatch(action)
 }

    abstract class ControllerView(
 val store: Store,
 mainThread: ThreadExecutor? = null): LifecycleCallbacks, StateHandler(mainThread) {
  56. protected fun <T : Action> dispatch(action: T) {
 store.dispatch(action)
 }

    abstract class ControllerView(
 val store: Store,
 mainThread: ThreadExecutor? = null): LifecycleCallbacks, StateHandler(mainThread) { abstract fun handleState(state: State) 
 }
  57. Action ACTION

  58. Action ACTION CREATE DELETE FETCH UPDATE

  59. Action ACTION CREATE DELETE FETCH UPDATE NAVIGATE OTHERS

  60. sealed class Action

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

    class ReorderItemsAction(val items: List<Item>) : 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()
 }
  62. sealed class Action sealed class UpdateAction : Action() {
 data

    class ReorderItemsAction(val items: List<Item>) : 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<Item>) : ReadAction()
 }
  63. STATE Reducer Store Store Remember, State is immutable.

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

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

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

    Action Store Remember, State is immutable.
  67. State enum class Navigation {
 ITEMS_LIST,
 EDIT_ITEM
 }
 data class

    ItemsListScreen(
 val items: List<Item> = 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.
  68. Store abstract class Store( val sideEffects: CopyOnWriteArrayList<SideEffect> = CopyOnWriteArrayList(),
 val

    stateHandlers: CopyOnWriteArrayList<StateHandler> = CopyOnWriteArrayList(),
 val storeThread: ThreadExecutor? = null) : Subscribers {
  69. Store abstract class Store( val sideEffects: CopyOnWriteArrayList<SideEffect> = CopyOnWriteArrayList(),
 val

    stateHandlers: CopyOnWriteArrayList<StateHandler> = CopyOnWriteArrayList(),
 val storeThread: ThreadExecutor? = null) : Subscribers { private fun handle(action: Action) {
 val newState = reduce(action, state)
 dispatch(newState)
 sideEffects.dispatch(action)
 }
  70. Store abstract class Store( val sideEffects: CopyOnWriteArrayList<SideEffect> = CopyOnWriteArrayList(),
 val

    stateHandlers: CopyOnWriteArrayList<StateHandler> = 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)
 } 
 . . .
  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)
 } }
  72. abstract class Reducer<in T : Action> {
 
 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
  73. Models

  74. STORE STORE MODELS Models

  75. STORE STORE MODELS PRESENTATION STORE MODELS + EXTENSIONS Models

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

    PERSISTENCE MODELS MAPPER
  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
  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
 }
  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()
  80. REDUCER Side Effect

  81. SIDE EFFECT 1 REDUCER Side Effect

  82. SIDE EFFECT 1 SIDE EFFECT 2 REDUCER Side Effect

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

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

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

    . . . Side Effect Action New action
  86. Threading? VIEW STORE PERSISTENCE SIDE EFFECT

  87. Threading? VIEW STORE PERSISTENCE SIDE EFFECT UI Thread

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

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

    Persistence Thread (Side Effect N Thread)
  90. Testing?

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

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

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

    tests on our ControllerViews. Unit + instrumentation tests in our Persistence layer (side effect).
  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.
  95. Demo 1!

  96. Demo 1!

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

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

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

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

    ITEM TO NOTE LIST NAVIGATION UPDATED FETCH LIST ITEMS LIST ITEMS Action State
  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
  102. Demo 2! Side effects are optional and decoupled elements.

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

  104. Some thoughts

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

    architectures.
  106. Some thoughts Unidirectional makes your code easy to follow. This

    is not the Philosopher Stone of the architectures.
  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.
  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.
  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.
  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.
  111. ? @CesarValiente @CesarValiente

  112. Thanks! @CesarValiente @CesarValiente

  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
  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/
  115. Image licenses Flux and Redux images are property of Facebook.

    Emojis by Emoji One (CC-BY): http://emojione.com/