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

LiveData beyond ViewModels by Marcos Damasceno

LiveData beyond ViewModels by Marcos Damasceno

1b77dd441f657f5aefb3e21283b252e6?s=128

GDG Montreal

November 24, 2018
Tweet

Transcript

  1. LiveData beyond ViewModels +Tasha Ramesh linkedin.com/in/akshataramesh Software Engineer @ Tinder

    +Marcos Damasceno @marcospaulosd Senior Software Engineer @ Tinder
  2. +Marcos Damasceno @marcospaulosd Senior Software Engineer @ Tinder +Tasha Ramesh

    linkedin.com/in/akshataramesh Software Engineer @ Tinder
  3. +Marcos Damasceno @marcospaulosd Senior Software Engineer @ Tinder +Tasha Ramesh

    linkedin.com/in/akshataramesh Software Engineer @ Tinder
  4. +Marcos Damasceno @marcospaulosd Senior Software Engineer @ Tinder +Tasha Ramesh

    linkedin.com/in/akshataramesh Software Engineer @ Tinder
  5. +Marcos Damasceno @marcospaulosd Senior Software Engineer @ Tinder +Tasha Ramesh

    linkedin.com/in/akshataramesh Software Engineer @ Tinder
  6. +Marcos Damasceno @marcospaulosd Senior Software Engineer @ Tinder

  7. +Akshata Ramesh Software Engineer @ Tinder Mobile apps in 2018

  8. +Akshata Ramesh Software Engineer @ Tinder Mobile apps in 2018

  9. +Akshata Ramesh Software Engineer @ Tinder Mobile apps in 2018

  10. +Akshata Ramesh Software Engineer @ Tinder Mobile apps in 2018

  11. +Akshata Ramesh Software Engineer @ Tinder Mobile apps in 2018

  12. Mobile apps in 2018

  13. Mobile apps in 2018

  14. Mobile apps in 2018

  15. None
  16. None
  17. None
  18. None
  19. CardContainer

  20. CardContainer CardContainer ContentContainer

  21. CardContainer ContentContainer CardContainer HeadlineText PreviewCarousel CardContainer ContentContainer

  22. CardContainer ContentContainer CardContainer HeadlineText PreviewCarousel CardContainer ContentContainer Summary Biography Anthem

    CardContainer ContentContainer CardContainer HeadlineText PreviewCarousel CardContainer ContentContainer
  23. None
  24. None
  25. Summary Biography Anthem CardContainer ContentContainer HeadlineText PreviewCarousel PageIndicator PicturesCarousel

  26. Summary Biography Anthem CardContainer ContentContainer HeadlineText PreviewCarousel PageIndicator PicturesCarousel

  27. Summary Biography Anthem CardContainer ContentContainer HeadlineText PreviewCarousel PageIndicator PicturesCarousel

  28. Summary Biography Anthem CardContainer ContentContainer HeadlineText PreviewCarousel PageIndicator PicturesCarousel

  29. Summary Biography Anthem CardContainer ContentContainer HeadlineText PreviewCarousel PageIndicator PicturesCarousel Summary

    Biography Anthem CardContainer ContentContainer HeadlineText PreviewCarousel PageIndicator PicturesCarousel
  30. Summary Biography Anthem CardContainer ContentContainer HeadlineText PreviewCarousel PageIndicator PicturesCarousel

  31. Summary Biography Anthem CardContainer ContentContainer HeadlineText PreviewCarousel PageIndicator PicturesCarousel

  32. Summary Biography Anthem CardContainer ContentContainer HeadlineText PreviewCarousel PageIndicator PicturesCarousel

  33. Fragments and Views

  34. View Fragment listener listener listener View listener listener listener Fragments

    and Views
  35. Activities, Fragments, and Views

  36. Activity Fragment Fragment View View View View View View View

    View Fragment Activities, Fragments, and Views
  37. Views are barebones Presenters handle the complexity of managing the

    Views Complex logic within the Presenter can be tested The MVP Approach
  38. Are Views still barebones? But they need to coordinate their

    children… Presenters gain more responsibility Complex logic within the Presenter is more tedious to test The MVP Approach - With Complex View Hierarchy
  39. UI Is Intrinsically Reactive

  40. click click click UI Is Intrinsically Reactive

  41. UI is asynchronous

  42. view.viewTreeObserver.addOnGlobalLayoutListener( object : ViewTreeObserver.OnGlobalLayoutListener { override fun onGlobalLayout() { view.viewTreeObserver.removeOnGlobalLayoutListener(this)

    ... ... ... } } )
  43. val newLayoutParams = getLayoutParams() newLayoutParams.width = 100 newLayoutParams.height = 200

    viewTreeObserver.addOnPreDrawListener( object : ViewTreeObserver.OnPreDrawListener { override fun onPreDraw(): Boolean { view.viewTreeObserver.removeOnPreDrawListener(this) ... } } ) setLayoutParams(newLayoutParams)
  44. view.viewTreeObserver.addOnPreDrawListener( object : ViewTreeObserver.OnPreDrawListener { override fun onPreDraw() { view.viewTreeObserver.removeOnPreDrawListener(this)

    ... ... ... } } )
  45. The True Nature of UI

  46. Embrace that UI is intrinsically reactive Android Framework API can

    be asynchronous UI needs to be able to handle configuration changes Components need to be choreographed and coordinated The True Nature of UI
  47. View Models

  48. ViewModels solve the need to handle UI across configuration changes

    View Models
  49. Allows to form a reactive bridge to the core of

    the app ViewModels solve the need to handle UI across configuration changes View Models
  50. Allows to form a reactive bridge to the core of

    the app Inherently bound to lifecycle of Activity or Fragment ViewModels solve the need to handle UI across configuration changes View Models
  51. None
  52. ViewModels, LiveData And Lifecycles, oh my - Lyla Fujiwara

  53. LiveData

  54. Bound to lifecycle of parent lifecycle owner (fragment, activity, etc)

    LiveData
  55. LiveData Bound to lifecycle of parent lifecycle owner (fragment, activity,

    etc) Can be used to form connections between UI components that need to be interactive
  56. LiveData Bound to lifecycle of parent lifecycle owner (fragment, activity,

    etc) Can be used to form connections between UI components that need to be interactive Acts like a BehaviorSubject from RxJava
  57. LiveData

  58. LiveData

  59. LiveData subscribe

  60. LiveData value_1 subscribe

  61. LiveData value_1 subscribe

  62. LiveData value_1 subscribe value_2

  63. LiveData value_1 subscribe value_2

  64. LiveData value_1 subscribe value_2

  65. LiveData value_1 subscribe subscribe value_2

  66. LiveData value_1 subscribe subscribe value_2

  67. LiveData value_1 subscribe subscribe value_2 value_3

  68. LiveData value_1 subscribe subscribe value_2 value_3

  69. LiveData value_1 subscribe subscribe value_2 value_3

  70. LiveData value_1 subscribe subscribe value_2 value_3

  71. LiveData value_1 subscribe subscribe value_2 value_3

  72. LiveData value_1 subscribe subscribe value_2 value_3

  73. LiveData - Transformations.map.switchMap

  74. LiveData version of operators LiveData - Transformations.map.switchMap

  75. LiveData version of Rx operators Out of the box we

    have Map and SwitchMap LiveData - Transformations.map.switchMap
  76. One-to-one static transformation Transformations.map.switchMap

  77. One-to-one static transformation Transformations.map.switchMap private val user = MutableLiveData<User>() val

    birthdate: LiveData<Date> = Transformations.map(user) { convertDate(it.birthDate) } x
  78. One-to-one static transformation Transformations.map.switchMap private val user = MutableLiveData<User>() val

    birthdate: LiveData<Date> = Transformations.map(user) { convertDate(it.birthDate) } x
  79. One-to-one static transformation Transformations.map.switchMap private val user = MutableLiveData<User>() val

    birthdate: LiveData<Date> = Transformations.map(user) { convertDate(it.birthDate) } x private fun convertDate(date: String) : Date { val dateFormatter = SimpleDateFormat.getDateInstance() return dateFormatter.parse(date) } x
  80. One-to-one static transformation Transformations.map.switchMap private val user = MutableLiveData<User>() val

    birthdate: LiveData<Date> = Transformations.map(user) { convertDate(it.birthDate) } x private fun convertDate(date: String) : Date { val dateFormatter = SimpleDateFormat.getDateInstance() return dateFormatter.parse(date) } x
  81. One-to-one static transformation Transformations.map.switchMap

  82. Transformations.switchMap

  83. Transformations.switchMap One-to-one dynamic transformation

  84. Transformations.switchMap One-to-one dynamic transformation private fun currentPhotoForIndex(index: Int) LiveData<String> {...}

  85. Transformations.switchMap One-to-one dynamic transformation private fun currentPhotoForIndex(index: Int) LiveData<String> {...}

    val currentPhotoIndex = LiveData<Int>
  86. Transformations.switchMap One-to-one dynamic transformation private fun currentPhotoForIndex(index: Int) LiveData<String> {...}

    val currentPhotoIndex = LiveData<Int> val currentPhotoByIndex = Transformations.switchMap(currentPhotoIndex) {index -> currentPhotoForIndex(index) }
  87. Treating UI as a Network

  88. Treating UI as a Network Activity Stopped Fragment Fragment Fragment

    Fragment Fragment Fragment Activity Started
  89. Treating UI as a Network - With ViewModels Activity Stopped

    Fragment Activity Started Fragment Fragment Fragment Fragment Fragment ViewModel ViewModel ViewModel ViewModel ViewModel ViewModel
  90. Fragments and ViewModel Imagine a Fragment or ViewGroup with many

    children all interacting with one another, and with their parents
  91. Fragments and ViewModel Imagine a Fragment or ViewGroup with many

    children all interacting with one another, and with their parents Even the ViewModel could get complex pretty quickly
  92. Fragments and ViewModel \ Imagine a Fragment or ViewGroup with

    many children all interacting with one another, and with their parents Even the ViewModel could get complex pretty quickly We’re using LiveData to handle granular View choreography within the Fragment…
  93. Fragments and ViewModel Fragment View View View View View ViewModel

    LiveData LiveData LiveData LiveData
  94. Can we simplify? If we took a step back and

    really looked at the changes that the UI goes through
  95. Can we simplify? If we took a step back and

    really looked at the changes that the UI goes through Certain transitions of the UI can be grouped together to form states
  96. Can we simplify? If we took a step back and

    really looked at the changes that the UI goes through Certain transitions of the UI can be grouped together to form states UI can react to those states not even caring what caused those states in the first place
  97. State 1 State 2 State 3 click click click

  98. UI and States with LiveData

  99. StateA StateB StateC StateLiveData click click click

  100. State Machines

  101. A B C Action Action Action Action Action Action State

    Machines State + Action = State Quantifiable manifestation of a reactive system. And since UI is intrinsically reactive...
  102. State Machines For when UX can be behaviorally and visually

    disassembled into distinct State + Action = State A B C Action Action Action Action Action Action states, actions, & side effects
  103. Fragment Button1 click listener drag listener Button2 click listener Button1Click

    Button1Drag Button2Click Actions
  104. Actions SideEffects Update Store Make Api Call Start Background Task

    Fragment Button1 click listener drag listener Button2 click listener Button1Click Button1Drag Button2Click
  105. States and Actions

  106. sealed class State { x abstract fun consumeAction(action: Action): State

    } x object StateA : x State() { x override fun consumeAction(action: Action): State { x return when (action) { x Button1Click -> StateB Button2Click -> StateC } } x } x States and Actions
  107. sealed class State { x abstract fun consumeAction(action: Action): State

    } x object StateA : x State() { x override fun consumeAction(action: Action): State { x return when (action) { x Button1Click -> StateB Button2Click -> StateC } } x } x A A States and Actions
  108. sealed class State { x abstract fun consumeAction(action: Action): State

    } x object StateA : x State() { x override fun consumeAction(action: Action): State { x return when (action) { x Button1Click -> StateB Button2Click -> StateC } } x } x A B Button1Click A States and Actions
  109. sealed class State { x abstract fun consumeAction(action: Action): State

    } x object StateA : x State() { x override fun consumeAction(action: Action): State { x return when (action) { x Button1Click -> StateB Button2Click -> StateC } } x } x Button2Click C States and Actions A B Button1Click A
  110. A A sealed class State(open val sideEffect: SideEffect? = null)

    { x abstract fun consumeAction(action: Action): State } x data class StateA( override val sideEffect: SideEffect? = null ) : State(sideEffect) { x override fun consumeAction(action: Action): State { x return when (action) { Button1Click -> StateB Button2Click -> StateC(SideEffect.UpdateStore) x } x } x } x States, Actions, & Side Effects
  111. A B Button1Click A sealed class State(open val sideEffect: SideEffect?

    = null) { x abstract fun consumeAction(action: Action): State } x data class StateA( override val sideEffect: SideEffect? = null ) : State(sideEffect) { x override fun consumeAction(action: Action): State { x return when (action) { Button1Click -> StateB Button2Click -> StateC(SideEffect.UpdateStore) x } x } x } x States, Actions, & Side Effects
  112. Button2Click C + Update Store sealed class State(open val sideEffect:

    SideEffect? = null) { x abstract fun consumeAction(action: Action): State } x data class StateA( override val sideEffect: SideEffect? = null ) : State(sideEffect) { x override fun consumeAction(action: Action): State { x return when (action) { Button1Click -> StateB Button2Click -> StateC(SideEffect.UpdateStore) x } x } x } x A B Button1Click A States, Actions, & Side Effects
  113. Transmitting States Using
 ViewModels & x LiveData

  114. Container Handle State Action State ViewModel StateLiveData Action Action Child

    view Child view listener listener listener Transmitting States Using ViewModels & x LiveData
  115. StateLiveData Current State Action Current State Action
 Consumer Action
 Consumer

    Action
 Consumer StateLiveData State
  116. class FlowViewModel : ViewModel() {x val stateTransitions = MutableLiveData<State>() init

    {x stateTransitions.value = StateA }x fun updateState(action: Action) {x val currentState = stateTransitions.value stateTransitions.value = currentState }x }x
  117. class FlowViewModel : ViewModel() {x val stateTransitions = MutableLiveData<State>() init

    {x stateTransitions.value = StateA }x fun updateState(action: Action) {x val currentState = stateTransitions.value stateTransitions.value = currentState }x }x
  118. class FlowViewModel : ViewModel() {x val stateTransitions = MutableLiveData<State>() init

    {x stateTransitions.value = StateA }x fun updateState(action: Action) {x val currentState = stateTransitions.value stateTransitions.value = currentState }x }x
  119. class FlowViewModel : ViewModel() {x val stateTransitions = MutableLiveData<State>() init

    {x stateTransitions.value = StateA }x fun updateState(action: Action) {x val currentState = stateTransitions.value stateTransitions.value = currentState }x }x
  120. class FlowViewModel : ViewModel() {x val stateTransitions = MutableLiveData<State>() init

    {x stateTransitions.value = StateA }x fun updateState(action: Action) {x val currentState = stateTransitions.value stateTransitions.value = currentState.consumeState(action) }x }x
  121. class FlowViewModel : ViewModel() {x val stateTransitions = MutableLiveData<State>() init

    {x stateTransitions.value = StateA }x fun updateState(action: Action) {x val currentState = stateTransitions.value stateTransitions.value = currentState.consumeState(action) }x }x
  122. class ParentFragment : Fragment() {x override fun onActivityCreated(savedInstanceState: Bundle?) {x

    super.onActivityCreated(savedInstanceState) . . . . . . flowViewModel.stateTransitions.observe(this, Observer<State> { state -> when (state) { is StateA -> { // configure appearance of child views for StateA }x is StateB -> { // configure appearance of child views for StateB }x is StateC -> { // configure appearance of child views for StateC }x }x })x }x }x
  123. class ParentFragment : Fragment() {x override fun onActivityCreated(savedInstanceState: Bundle?) {x

    super.onActivityCreated(savedInstanceState) . . . . . . flowViewModel.stateTransitions.observe(this, Observer<State> { state -> when (state) { is StateA -> { // configure appearance of child views for StateA }x is StateB -> { // configure appearance of child views for StateB }x is StateC -> { // configure appearance of child views for StateC }x }x })x }x }x
  124. class ParentFragment : Fragment() {x override fun onActivityCreated(savedInstanceState: Bundle?) {x

    super.onActivityCreated(savedInstanceState) flowViewModel.stateTransitions.observe(this, Observer<State> { state -> . . . })x }x override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) button1.setOnClickListener { flowViewModel.updateState(Action.Button1Click) }x . . . . . . }x }x
  125. class ParentFragment : Fragment() {x override fun onActivityCreated(savedInstanceState: Bundle?) {x

    super.onActivityCreated(savedInstanceState) flowViewModel.stateTransitions.observe(this, Observer<State> { state -> . . . })x flowViewModel.sideEffects.observe(this, Observer<SideEffect> { sideEffect -> handleSideEffect(sideEffect) })x }x x }x
  126. Tinder Chat: Stateful systems

  127. Tinder Chat: Stateful systems GIF button

  128. Tinder Chat: Stateful systems Bitmoji button

  129. state + action = state TextInputUnfocused + ClickGifButton

  130. GifInputFocused state + action = state TextInputUnfocused + ClickGifButton

  131. state + action = state GifInputFocused + ClickBitmojiButton

  132. state + action = state BitmojiUnfocused GifInputFocused + ClickBitmojiButton

  133. state + action = state BitmojiUnfocused + ClickEditText

  134. state + action = state TextInputFocused + ClickEditText BitmojiUnfocused

  135. state + action = state TextInputFocused + ClickBackButton

  136. state + action = state TextInputUnfocused TextInputFocused + ClickBackButton

  137. state + action = state TextInputUnfocused + ClickGifButton

  138. GifInputFocused state + action = state TextInputUnfocused + ClickGifButton

  139. state + action = state GifInputFocused + ClickBitmojiButton

  140. state + action = state BitmojiSearchUnfocused GifInputFocused + ClickBitmojiButton

  141. state + action = state BitmojiSearchUnfocused + ClickEditText

  142. state + action = state TextInputFocused BitmojiSearchUnfocused + ClickEditText

  143. Handle sub-states with custom LiveData

  144. TextInputFocused stateTransitions GifInputUnfocused BitmojiUnfocused stateTransitions.buttonGroupSubStates AllInactive GifButtonActive BitmojiButtonActive TextInputUnfocused AllInactive

    Handle sub-states with custom LiveData
  145. class ParentFragment : Fragment() {x override fun onActivityCreated(savedInstanceState: Bundle?) {x

    super.onActivityCreated(savedInstanceState) flowViewModel.stateTransitions.observe(this, Observer<State> { state -> . . . }) flowViewModel.stateTransitions.buttonGroupSubstates.observe( this, Observer<ButtonGroupState> { buttonGroupStates -> // configure appearance of child buttons within button group } )x }x }x
  146. class StateTransitions(initialState: State) : MutableLiveData<State>() {x init {x value =

    initialState }x val buttonGroupSubStates: LiveData<ButtonGroupState>x get() = Transformations.map(this) {x state ->x when (state) {x TextInputUnfocused -> ButtonGroupState.AllInactive TextInputFocused -> ButtonGroupState.AllInactive GifInputUnfocused -> ButtonGroupState.GifButtonActive BitmojiUnfocused -> ButtonGroupState.BitmojiButtonActive else -> . . .x }x }x fun updateState(action: Action) {x value = value?.consumeAction(action)x }x }x
  147. class StateTransitions(initialState: State) : MutableLiveData<State>() {x init {x value =

    initialState }x val buttonGroupSubStates: LiveData<ButtonGroupState>x get() = Transformations.map(this) {x state ->x when (state) {x TextInputUnfocused -> ButtonGroupState.AllInactive TextInputFocused -> ButtonGroupState.AllInactive GifInputUnfocused -> ButtonGroupState.GifButtonActive BitmojiUnfocused -> ButtonGroupState.BitmojiButtonActive else -> . . .x }x }x fun updateState(action: Action) {x value = value?.consumeAction(action)x }x }x
  148. class StateTransitions(initialState: State) : MutableLiveData<State>() {x init {x value =

    initialState }x val buttonGroupSubStates: LiveData<ButtonGroupState>x get() = Transformations.map(this) {x state ->x when (state) {x TextInputUnfocused -> ButtonGroupState.AllInactive TextInputFocused -> ButtonGroupState.AllInactive GifInputUnfocused -> ButtonGroupState.GifButtonActive BitmojiUnfocused -> ButtonGroupState.BitmojiButtonActive else -> . . .x }x }x fun updateState(action: Action) {x value = value?.consumeAction(action)x }x }x class StateTransitions(initialState: State) : MutableLiveData<State>() {x init {x value = initialState }x val buttonGroupSubStates: LiveData<ButtonGroupState>x get() = Transformations.map(this) {x state ->x when (state) {x TextInputUnfocused -> ButtonGroupState.AllInactive TextInputFocused -> ButtonGroupState.AllInactive GifInputUnfocused -> ButtonGroupState.GifButtonActive BitmojiUnfocused -> ButtonGroupState.BitmojiButtonActive else -> . . .x }x }x fun updateState(action: Action) {x value = value?.consumeAction(action)x }x }x
  149. class StateTransitions(initialState: State) : MutableLiveData<State>() {x init {x value =

    initialState }x val buttonGroupSubStates: LiveData<ButtonGroupState>x get() = Transformations.map(this) {x state ->x when (state) {x TextInputUnfocused -> ButtonGroupState.AllInactive TextInputFocused -> ButtonGroupState.AllInactive GifInputUnfocused -> ButtonGroupState.GifButtonActive BitmojiUnfocused -> ButtonGroupState.BitmojiButtonActive else -> . . .x }x }x fun updateState(action: Action) {x value = value?.consumeAction(action)x }x }x class StateTransitions(initialState: State) : MutableLiveData<State>() {x init {x value = initialState }x val buttonGroupSubStates: LiveData<ButtonGroupState>x get() = Transformations.map(this) {x state ->x when (state) {x TextInputUnfocused -> ButtonGroupState.AllInactive TextInputFocused -> ButtonGroupState.AllInactive GifInputUnfocused -> ButtonGroupState.GifButtonActive BitmojiUnfocused -> ButtonGroupState.BitmojiButtonActive else -> . . .x }x }x fun updateState(action: Action) {x value = value?.consumeAction(action)x }x }x
  150. ViewModels + LiveData + States

  151. Simplifies interaction between child views and their parents Ensures that

    all UI elements behave predictably - even across hard to test UI interactions - even across configuration changes ViewModels + LiveData + States Offers the flexibility to create UI abstractions
  152. Taking this further…

  153. Containers can also be ViewGroups, not just Fragments Using data

    binding, we can reduce the boilerplate of binding ViewModels/LiveData to container or child ViewGroups Taking this further… Custom implementations of LiveData can serve as further abstractions
  154. LiveData beyond ViewModels +Tasha Ramesh linkedin.com/in/akshataramesh Software Engineer @ Tinder

    +Marcos Damasceno @marcospaulosd Senior Software Engineer @ Tinder