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

Cesar Valiente

May 02, 2017
Tweet

More Decks by Cesar Valiente

Other Decks in Programming

Transcript

  1. Global app state +1 State = 1 Next Value =

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

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

    3 Current Value = 3 State is immutable.
  4. KUnidirectional OSS sample app which shows this architecture. A simple

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

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

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

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

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

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

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

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

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

    User interaction UI Data Optional Action SIDE EFFECT N
  15. Dependencies VIEW ANDROID DEPENDENCIES NO FRAMEWORK DEPENDENCIES SIDE EFFECT 1

    ANDROID DEPENDENCIES SIDE EFFECT N NO FRAMEWORK DEPENDENCIES ACTION REDUCER CONTROLLER-VIEW STORE
  16. VIEW User interaction User interaction New UI data to render

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

    User result View CONTROLLER-VIEW ACTIVITY / FRAGMENT
  18. protected fun <T : Action> dispatch(action: T) {
 store.dispatch(action)
 }

    abstract class ControllerView(
 val store: Store,
 mainThread: ThreadExecutor? = null): LifecycleCallbacks, StateHandler(mainThread) {
  19. 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) 
 }
  20. 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()
 }
  21. 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()
 }
  22. STATE Reducer ACTION NEW STATE Store New state Action Action

    Action Store Remember, State is immutable.
  23. 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.
  24. Store abstract class Store( val sideEffects: CopyOnWriteArrayList<SideEffect> = CopyOnWriteArrayList(),
 val

    stateHandlers: CopyOnWriteArrayList<StateHandler> = CopyOnWriteArrayList(),
 val storeThread: ThreadExecutor? = null) : Subscribers {
  25. 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)
 }
  26. 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)
 } 
 . . .
  27. 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)
 } }
  28. 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
  29. 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
  30. 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
 }
  31. 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()
  32. SIDE EFFECT N SIDE EFFECT 1 SIDE EFFECT 2 REDUCER

    . . . Side Effect Action New action
  33. Testing? UI tests on Presentation layer (View). Unit + Integration

    tests on our ControllerViews. Unit + instrumentation tests in our Persistence layer (side effect).
  34. 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.
  35. Demo 1! OPEN EDIT ITEM NAVIGATION UPDATED SAVE ITEM NEW

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

    ITEM TO NOTE LIST NAVIGATION UPDATED FETCH LIST ITEMS LIST ITEMS Action State
  37. 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
  38. Some thoughts Unidirectional makes your code easy to follow. This

    is not the Philosopher Stone of the architectures.
  39. 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.
  40. 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.
  41. 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.
  42. 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.
  43. 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
  44. 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/
  45. Image licenses Flux and Redux images are property of Facebook.

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