Handling View State and Events with RainbowCake (Conference for Kotliners 2020)

Handling View State and Events with RainbowCake (Conference for Kotliners 2020)

RainbowCake is an architectural framework built on top of the best & latest of Jetpack and Kotlin. One of its core features is its view state and view event handling mechanism, which is somewhere between MVVM and MVI, and is powered by LiveData. In this talk, we’ll first look at the client side API of the framework, and the design decisions that went into it. Then, we’ll dive into how all of this is implemented under the hood by customizing LiveData. While the talk discusses this specific framework, the ideas in it are universal and can be used independently in any project.

4047c64e3a1e2f81addd4ba675ddc451?s=128

Márton Braun

June 05, 2020
Tweet

Transcript

  1. Handling View State and Events with RainbowCake Márton Braun zsmb.co

    zsmb13
  2. RainbowCake rainbowcake/rainbowcake

  3. RainbowCake rainbowcake/rainbowcake An Android architecture framework library test

  4. RainbowCake rainbowcake/rainbowcake An Android architecture library concept framework

  5. RainbowCake rainbowcake/rainbowcake An Android architecture concept thingie library

  6. RainbowCake rainbowcake/rainbowcake An Android architecture thingie framework concept

  7. RainbowCake rainbowcake/rainbowcake An Android architecture framework test thingie

  8. RainbowCake rainbowcake/rainbowcake An Android architecture framework libraries

  9. RainbowCake rainbowcake/rainbowcake An Android architecture framework libraries guidance

  10. RainbowCake rainbowcake/rainbowcake An Android architecture framework libraries guidance

  11. RainbowCake rainbowcake/rainbowcake Threading Dependency injection Layers View state handling Navigation

    Models
  12. MvRx airbnb/MvRx Orbit MVI babylonhealth/orbit-mvi Roxie ww-tech/roxie Mosby sockeqwe/mosby Eiffel

    etiennelenhart/Eiffel Uniflow uniflow-kt/uniflow-kt MVICore badoo/MVICore RainbowCake rainbowcake/rainbowcake Mobius spotify/mobius
  13. 2020 2019 Start of development Moved into a library Workshop

    Modules Koin support Testing module Repackage, website launch GitHub, MavenCentral AndroidX 1.0 MVICore MvRx Orbit MVI Uniflow Roxie [0]
  14. ItemApi ItemDao NetworkDataSource DiskDataSource ShopInteractor UserInteractor ProfilePresenter CartPresenter HomePresenter ProfileViewModel

    CartViewModel HomeViewModel CartViewState ProfileViewState HomeViewState CartFragment ProfileFragment HomeFragment
  15. Dao Data source Interactor Presenter ViewModel ViewState Fragment

  16. ViewState ViewModel Presenter Interactor Data source Dao Fragment

  17. ViewModel Presenter Interactor Data source Fragment

  18. RainbowCake

  19. Data source Interactor Presenter ViewModel Fragment

  20. MVVM View access through an interface Separation of View and

    business logic Component between View and Model MVP
  21. MVVM View access through an interface Separation of View and

    business logic Component between View and Model MVP Separation of View and business logic
  22. MVVM View access through an interface Separation of View and

    business logic Component between View and Model MVP Separation of View and business logic Component between View and Model
  23. MVVM View access through an interface Separation of View and

    business logic Component between View and Model MVP View access through an interface Separation of View and business logic Component between View and Model
  24. Observable state exposed to the View MVVM View access through

    an interface Separation of View and business logic Component between View and Model MVP View access through an interface Separation of View and business logic Component between View and Model
  25. View access through an interface MVVM Separation of View and

    business logic Component between View and Model Observable state exposed to the View MVP View access through an interface Separation of View and business logic Component between View and Model
  26. MVVM Separation of View and business logic Component between View

    and Model Observable state exposed to the View Data binding* MVP View access through an interface Separation of View and business logic Component between View and Model
  27. MVVM Separation of View and business logic Component between View

    and Model Observable state exposed to the View Data binding* MVI
  28. MVVM Separation of View and business logic Component between View

    and Model Observable state exposed to the View Data binding* MVI Single View state object
  29. MVVM Separation of View and business logic Component between View

    and Model Observable state exposed to the View Data binding* MVI Single View state object Reactive data streams
  30. MVVM Separation of View and business logic Component between View

    and Model Observable state exposed to the View Data binding* MVI Single View state object Reactive data streams Input events as objects
  31. MVVM Separation of View and business logic Component between View

    and Model Observable state exposed to the View Data binding* MVI Single View state object Reactive data streams Input events as objects Reducer
  32. MVVM Separation of View and business logic Component between View

    and Model Observable state exposed to the View Data binding* MVI Single View state object Reactive data streams Input events as objects Reducer [1]
  33. State handling

  34. None
  35. app RainbowCake

  36. app RainbowCake abstract class RainbowCakeViewModel ViewModel() class ProfileViewModel : RainbowCakeViewModel()

    :
  37. app RainbowCake abstract class RainbowCakeViewModel ViewModel() { protected val _state:

    MutableLiveData<?> = MutableLiveData() val state: LiveData<?> = _state } class ProfileViewModel : RainbowCakeViewModel() :
  38. app RainbowCake abstract class RainbowCakeViewModel ViewModel() { protected val _state:

    MutableLiveData<?> = MutableLiveData() val state: LiveData<?> = _state } class ProfileViewModel : RainbowCakeViewModel() :
  39. app RainbowCake abstract class RainbowCakeViewModel ViewModel() { protected val _state:

    MutableLiveData<?> = MutableLiveData() val state: LiveData<?> = _state } class ProfileViewModel : RainbowCakeViewModel() :
  40. app RainbowCake abstract class RainbowCakeViewModel ViewModel() { protected val _state:

    MutableLiveData<?> = MutableLiveData() val state: LiveData<?> = _state } class ProfileViewModel : RainbowCakeViewModel() :
  41. app RainbowCake abstract class RainbowCakeViewModel<VS : Any> ViewModel() { protected

    val _state: MutableLiveData<VS> = MutableLiveData() val state: LiveData<VS> = _state } class ProfileViewModel : RainbowCakeViewModel() :
  42. app RainbowCake abstract class RainbowCakeViewModel<VS : Any> ViewModel() { protected

    val _state: MutableLiveData<VS> = MutableLiveData() val state: LiveData<VS> = _state } class ProfileViewModel : RainbowCakeViewModel() :
  43. app RainbowCake abstract class RainbowCakeViewModel<VS : Any> : ViewModel() {

    protected val _state: MutableLiveData<VS> = MutableLiveData() val state: LiveData<VS> = _state } class ProfileViewModel : RainbowCakeViewModel<ProfileViewState>()
  44. app RainbowCake abstract class RainbowCakeViewModel<VS : Any> : ViewModel() {

    protected val _state: MutableLiveData<VS> = MutableLiveData() val state: LiveData<VS> = _state } class ProfileViewModel : RainbowCakeViewModel<ProfileViewState>() sealed class ProfileViewState object Loading : ProfileViewState() data class ProfileLoaded(val name: String) : ProfileViewState() [2]
  45. app RainbowCake abstract class RainbowCakeViewModel<VS : Any> : ViewModel() {

    protected val _state: MutableLiveData<VS> = MutableLiveData() val state: LiveData<VS> = _state } class ProfileViewModel : RainbowCakeViewModel<ProfileViewState>()
  46. app RainbowCake abstract class RainbowCakeViewModel<VS : Any> protected val _state:

    MutableLiveData<VS> = MutableLiveData() val state: LiveData<VS> = _state.distinct() } class ProfileViewModel : RainbowCakeViewModel<ProfileViewState>() : ViewModel() { [3]
  47. app RainbowCake abstract class RainbowCakeViewModel<VS : Any>( initialState: VS )

    protected val _state: MutableLiveData<VS> = MutableLiveData() val state: LiveData<VS> = _state.distinct() } class ProfileViewModel : RainbowCakeViewModel<ProfileViewState>() : ViewModel() {
  48. app RainbowCake abstract class RainbowCakeViewModel<VS : Any>( initialState: VS )

    : ViewModel() { protected val _state: MutableLiveData<VS> = MutableLiveData() val state: LiveData<VS> = _state.distinct() init { _state.value = initialState } } class ProfileViewModel : RainbowCakeViewModel<ProfileViewState>()
  49. app RainbowCake abstract class RainbowCakeViewModel<VS : Any>( initialState: VS )

    : ViewModel() { protected val _state: MutableLiveData<VS> = MutableLiveData() val state: LiveData<VS> = _state.distinct() init { _state.value = initialState } } class ProfileViewModel : RainbowCakeViewModel<ProfileViewState>(Loading) { fun loadProfile() { viewModelScope.launch { _state.value = Loading
  50. app RainbowCake abstract class RainbowCakeViewModel<VS : Any>( initialState: VS )

    : ViewModel() { protected val _state: MutableLiveData<VS> = MutableLiveData() val state: LiveData<VS> = _state.distinct() init { _state.value = initialState } } class ProfileViewModel : RainbowCakeViewModel<ProfileViewState>(Loading) { fun loadProfile() { viewModelScope.launch { _state.value = Loading _state.value = ProfileLoaded(loadUserName()) } } }
  51. app RainbowCake abstract class RainbowCakeViewModel<VS : Any>( initialState: VS )

    : ViewModel() { protected val _state: MutableLiveData<VS> = MutableLiveData() val state: LiveData<VS> = _state.distinct() init { _state.value = initialState } } class ProfileViewModel : RainbowCakeViewModel<ProfileViewState>(Loading) { fun loadProfile() { viewModelScope.launch { _state.value = _state.value = } } } _state.value!!.copy(isLoading = true) _state.value!!.copy(name = ) loadUserName()
  52. RainbowCake abstract class RainbowCakeViewModel<VS : Any>( initialState: VS ) :

    ViewModel() { protected val _state: MutableLiveData<VS> = MutableLiveData() val state: LiveData<VS> = _state.distinct() init { _state.value = initialState } }
  53. RainbowCake protected var viewState: VS get() = _state.value!! set(value) {

    _state.postValue(value) } abstract class RainbowCakeViewModel<VS : Any>( initialState: VS ) : ViewModel() { } val state: LiveData<VS> = _state.distinct() init { _state.value = initialState } val _state: MutableLiveData<VS> = MutableLiveData() private
  54. app RainbowCake class ProfileViewModel : RainbowCakeViewModel<ProfileViewState>(Loading) { fun loadProfile() {

    viewModelScope.launch { _state.value = _state.value!!.copy(isLoading = true) _state.value = _state.value!!.copy(name = loadUserName()) } } } abstract class RainbowCakeViewModel<VS : Any>( initialState: VS ) : ViewModel() { protected var viewState: VS get() = _state.value!! set(value) { _state.postValue(value) } }
  55. app RainbowCake = = viewState viewState viewState viewState .copy(isLoading =

    true) .copy(name = loadUserName()) class ProfileViewModel : RainbowCakeViewModel<ProfileViewState>(Loading) { fun loadProfile() { viewModelScope.launch { } } } abstract class RainbowCakeViewModel<VS : Any>( initialState: VS ) : ViewModel() { protected var viewState: VS get() = _state.value!! set(value) { _state.postValue(value) } }
  56. app RainbowCake loadUserName() ( ) Loading ProfileLoaded abstract class RainbowCakeViewModel<VS

    : Any>( initialState: VS ) : ViewModel() { protected var viewState: VS get() = _state.value!! set(value) { _state.postValue(value) } } class ProfileViewModel : RainbowCakeViewModel<ProfileViewState>(Loading) { fun loadProfile() { viewModelScope.launch { viewState = viewState = } } }
  57. app RainbowCake abstract class RainbowCakeViewModel<VS : Any>( initialState: VS )

    : ViewModel() { protected var viewState: VS get() = _state.value!! set(value) { _state.postValue(value) } } class ProfileViewModel : RainbowCakeViewModel<ProfileViewState>(Loading) { fun loadProfile() { viewModelScope.launch { viewState = Loading viewState = ProfileLoaded(loadUserName()) } } }
  58. app RainbowCake abstract class RainbowCakeViewModel<VS : Any>( initialState: VS )

    : ViewModel() { protected var viewState: VS get() = _state.value!! set(value) { _state.postValue(value) } } class ProfileViewModel : RainbowCakeViewModel<ProfileViewState>(Loading) { fun loadProfile() { viewModelScope.launch { viewState = Loading viewState = ProfileLoaded(loadUserName()) } } }
  59. app RainbowCake abstract class RainbowCakeViewModel<VS : Any>( initialState: VS )

    : ViewModel() { protected var viewState: VS get() = _state.value!! set(value) { _state.setValue(value) } } class ProfileViewModel : RainbowCakeViewModel<ProfileViewState>(Loading) { fun loadProfile() { viewModelScope.launch { viewState = Loading viewState = ProfileLoaded(loadUserName()) } } }
  60. app RainbowCake abstract class RainbowCakeViewModel<VS : Any>( initialState: VS )

    : ViewModel() { protected var viewState: VS get() = _state.value!! set(value) { _state.postValue(value) } } class ProfileViewModel : RainbowCakeViewModel<ProfileViewState>(Loading) { fun loadProfile() { viewModelScope.launch { viewState = Loading viewState = ProfileLoaded(loadUserName()) } } }
  61. app RainbowCake abstract class RainbowCakeViewModel<VS : Any>( initialState: VS )

    : ViewModel() { protected var viewState: VS get() = _state.value!! set(value) { _state.postValue(value) } } println(viewState is Loading) class ProfileViewModel : RainbowCakeViewModel<ProfileViewState>(Loading) { fun loadProfile() { viewModelScope.launch { viewState = Loading viewState = ProfileLoaded(loadUserName()) } } } [4]
  62. app RainbowCake ) : ViewModel() { protected var viewState: VS

    get() = _state.value!! set(value) { _state.postValue(value) } } class ProfileViewModel : RainbowCakeViewModel<ProfileViewState>(Loading) { fun loadProfile() {
  63. app RainbowCake abstract class RainbowCakeFragment : Fragment() class ProfileFragment :

    RainbowCakeFragment()
  64. RainbowCake { protected lateinit var viewModel: ? } app class

    ProfileFragment : RainbowCakeFragment() abstract class RainbowCakeFragment : Fragment()
  65. app RainbowCake abstract class RainbowCakeFragment<VM : RainbowCakeViewModel> Fragment() { protected

    lateinit var viewModel VM } class ProfileFragment : RainbowCakeFragment() : :
  66. app RainbowCake abstract class RainbowCakeFragment<VM : RainbowCakeViewModel> : Fragment() {

    protected lateinit var viewModel: VM } class ProfileFragment : RainbowCakeFragment<ProfileViewModel>()
  67. app RainbowCake abstract class RainbowCakeFragment<VM RainbowCakeViewModel<?>> : Fragment() { protected

    lateinit var viewModel: VM } class ProfileFragment : RainbowCakeFragment<ProfileViewModel>() :
  68. RainbowCake ? app abstract class RainbowCakeFragment<VS : Any, VM RainbowCakeViewModel<VS>>

    protected lateinit var viewModel: VM } class ProfileFragment : RainbowCakeFragment<ProfileViewModel>() : : Fragment(
  69. app RainbowCake abstract class RainbowCakeFragment<VS : Any, VM : RainbowCakeViewModel<VS>>

    protected lateinit var viewModel: VM } class ProfileFragment : RainbowCakeFragment<ProfileViewModel>() : Fragment() {
  70. app RainbowCake abstract class RainbowCakeFragment<VS : Any, VM : RainbowCakeViewModel<VS>>

    : Fragment() { protected lateinit var viewModel: VM } class ProfileFragment : RainbowCakeFragment<ProfileViewState, ProfileViewModel>()
  71. RainbowCake abstract class RainbowCakeFragment<VS Any, VM : RainbowCakeViewModel<VS>> : Fragment()

    { protected lateinit var viewModel abstract fun provideViewModel(): VM @CallSuper override fun onAttach(context: Context) { super.onAttach(context) viewModel = provideViewModel() } app class ProfileFragment : RainbowCakeFragment<ProfileViewState, ProfileViewModel>() : : VM }
  72. RainbowCake app class ProfileFragment : RainbowCakeFragment<ProfileViewState, ProfileViewModel>() { } abstract

    fun provideViewModel(): VM @CallSuper override fun onAttach(context: Context) { super.onAttach(context) viewModel = provideViewModel() } override fun provideViewModel() = getViewModelFromFactory() abstract class RainbowCakeFragment<VS : Any, VM : RainbowCakeViewModel<VS>> : Fragment() { protected lateinit var viewModel: VM } [5]
  73. RainbowCake app class ProfileFragment : RainbowCakeFragment<ProfileViewState, ProfileViewModel>() { } abstract

    fun provideViewModel(): VM @CallSuper override fun onAttach(context: Context) { super.onAttach(context) viewModel = provideViewModel() } override fun provideViewModel() = getViewModelFromFactory() abstract class RainbowCakeFragment<VS : Any, VM : RainbowCakeViewModel<VS>> : Fragment() { protected lateinit var viewModel: VM }
  74. RainbowCake @CallSuper override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view,

    savedInstanceState) viewModel.state.observe(viewLifecycleOwner, Observer { viewState -> }) } app class ProfileFragment : RainbowCakeFragment<ProfileViewState, ProfileViewModel>() { } abstract class RainbowCakeFragment<VS : Any, VM : RainbowCakeViewModel<VS>> : Fragment() { protected lateinit var viewModel: VM }
  75. RainbowCake @CallSuper override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view,

    savedInstanceState) viewModel.state.observe(viewLifecycleOwner, Observer { viewState -> }) } app class ProfileFragment : RainbowCakeFragment<ProfileViewState, ProfileViewModel>() { } abstract class RainbowCakeFragment<VS : Any, VM : RainbowCakeViewModel<VS>> : Fragment() { protected lateinit var viewModel: VM } [6]
  76. app RainbowCake : protected lateinit var viewModel: VM abstract class

    RainbowCakeFragment<VS : Any, VM : RainbowCakeViewModel<VS>> : Fragment() { abstract fun render(viewState VS) } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) viewModel.state.observe(viewLifecycleOwner, Observer { viewState -> viewState?.let { render(it) } }) } @CallSuper } class ProfileFragment : RainbowCakeFragment<ProfileViewState, ProfileViewModel>() { override fun render(viewState: ProfileViewState) { when (viewState) { Loading -> { ... }
  77. app RainbowCake override fun render(viewState: ProfileViewState) { when (viewState) {

    Loading -> { ... } is ProfileLoaded -> { ... } } } : abstract fun render(viewState VS) } abstract class RainbowCakeFragment<VS : Any, VM : RainbowCakeViewModel<VS>> : Fragment() { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) viewModel.state.observe(viewLifecycleOwner, Observer { viewState -> viewState?.let { render(it) } }) } class ProfileFragment : RainbowCakeFragment<ProfileViewState, ProfileViewModel>() { }
  78. app override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState)

    RainbowCake abstract class RainbowCakeFragment<VS : Any, VM : RainbowCakeViewModel<VS>> : Fragment() { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) viewModel.state.observe(viewLifecycleOwner, Observer { viewState -> viewState?.let { render(it) } }) } abstract fun render(viewState: VS) } override fun render(viewState: ProfileViewState) { when (viewState) { Loading -> { ... } is ProfileLoaded -> { ... } } } class ProfileFragment : RainbowCakeFragment<ProfileViewState, ProfileViewModel>() { } .exhaustive [2]
  79. app override fun render(viewState: ProfileViewState) { when (viewState) { Loading

    -> { ... } is ProfileLoaded -> { ... } } } class ProfileFragment : RainbowCakeFragment<ProfileViewState, ProfileViewModel>() { } .exhaustive override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) viewModel.loadProfile() }
  80. Event handling

  81. RainbowCake app abstract class RainbowCakeViewModel<VS : Any>( initialState: VS )

    : ViewModel() { } class ProfileViewModel : RainbowCakeViewModel<ProfileViewState>(Loading)
  82. RainbowCake app class ProfileViewModel : RainbowCakeViewModel<ProfileViewState>(Loading) abstract class RainbowCakeViewModel<VS :

    Any>( initialState: VS ) : ViewModel() { } SingleShotLiveData<?>() events: MutableLiveData<?> = val
  83. RainbowCake app class ProfileViewModel : RainbowCakeViewModel<ProfileViewState>(Loading) abstract class RainbowCakeViewModel<VS :

    Any>( initialState: VS ) : ViewModel() { } SingleShotLiveData<?>() events: MutableLiveData<?> = val [7]
  84. RainbowCake app class ProfileViewModel : RainbowCakeViewModel<ProfileViewState>(Loading) abstract class RainbowCakeViewModel<VS :

    Any>( initialState: VS ) : ViewModel() { protected _ val events: LiveData<?> = _events } SingleShotLiveData<?>() val events: MutableLiveData<?> =
  85. RainbowCake app class ProfileViewModel : RainbowCakeViewModel<ProfileViewState>(Loading) abstract class RainbowCakeViewModel<VS :

    Any, VE : Any>( initialState: VS ) : ViewModel() { protected val _events: MutableLiveData<VE> = SingleShotLiveData<VE>() val events: LiveData<VE> = _events }
  86. RainbowCake app class ProfileViewModel : RainbowCakeViewModel<ProfileViewState>(Loading) abstract class RainbowCakeViewModel<VS :

    Any, VE : Any>( initialState: VS ) : ViewModel() { protected val _events: MutableLiveData<VE> = SingleShotLiveData<VE>() val events: LiveData<VE> = _events } abstract class RainbowCakeFragment< VS : Any, VE : Any, VM : RainbowCakeViewModel<VS, VE> > : Fragment
  87. RainbowCake app class ProfileViewModel : RainbowCakeViewModel<ProfileViewState, ProfileViewEve abstract class RainbowCakeViewModel<VS

    : Any, VE : Any>( initialState: VS ) : ViewModel() { protected val _events: MutableLiveData<VE> = SingleShotLiveData<VE>() val events: LiveData<VE> = _events } abstract class RainbowCakeFragment< VS : Any, VE : Any, VM : RainbowCakeViewModel<VS, VE> > : Fragment
  88. RainbowCake app class ProfileViewModel : RainbowCakeViewModel<ProfileViewState>(Loading) abstract class RainbowCakeViewModel<VS :

    Any>( initialState: VS ) : ViewModel() { protected val _events: MutableLiveData<?> = SingleShotLiveData<?>() val events: LiveData<?> = _events }
  89. RainbowCake app class ProfileViewModel : RainbowCakeViewModel<ProfileViewState>(Loading) abstract class RainbowCakeViewModel<VS :

    Any>( initialState: VS ) : ViewModel() { protected val _events: MutableLiveData<?> = SingleShotLiveData<?>() val events: LiveData<?> = _events } interface OneShotEvent
  90. RainbowCake app class ProfileViewModel RainbowCakeViewModel<ProfileViewState>(Loading) abstract class RainbowCakeViewModel<VS : Any>(

    initialState: VS ) : ViewModel() { protected val _events: MutableLiveData<OneShotEvent> = SingleShotLiveData<OneShotEvent>() val events: LiveData<OneShotEvent> = _events } interface OneShotEvent : { object SaveFailedEvent : OneShotEvent
  91. RainbowCake private app class ProfileViewModel RainbowCakeViewModel<ProfileViewState>(Loading) { object SaveFailedEvent :

    OneShotEvent fun saveProfile() { _events.value = SaveFailedEvent } } interface OneShotEvent : } val events: LiveData<OneShotEvent> = _events abstract class RainbowCakeViewModel<VS : Any>( initialState: VS ) : ViewModel() { SingleShotLiveData<OneShotEvent>() val _events: MutableLiveData<OneShotEvent> = protected
  92. RainbowCake private app class ProfileViewModel RainbowCakeViewModel<ProfileViewState>(Loading) { object SaveFailedEvent :

    OneShotEvent fun saveProfile() { _events.value = SaveFailedEvent } } interface OneShotEvent : } val events: LiveData<OneShotEvent> = _events abstract class RainbowCakeViewModel<VS : Any>( initialState: VS ) : ViewModel() { SingleShotLiveData<OneShotEvent>() val _events: MutableLiveData<OneShotEvent> = protected
  93. app RainbowCake protected fun postEvent(event: OneShotEvent) { _events.postValue(event) } interface

    OneShotEvent } val events: LiveData<OneShotEvent> = _events abstract class RainbowCakeViewModel<VS : Any>( initialState: VS ) : ViewModel() { SingleShotLiveData<OneShotEvent>() val _events: MutableLiveData<OneShotEvent> = private class ProfileViewModel RainbowCakeViewModel<ProfileViewState>(Loading) { object SaveFailedEvent : OneShotEvent fun saveProfile() { _events.value = SaveFailedEvent } } :
  94. app RainbowCake abstract class RainbowCakeViewModel<VS : Any>( initialState: VS )

    : ViewModel() { private val _events: MutableLiveData<OneShotEvent> = SingleShotLiveData<OneShotEvent>() val events: LiveData<OneShotEvent> = _events protected fun postEvent(event: OneShotEvent) { _events.postValue(event) } } interface OneShotEvent class ProfileViewModel : RainbowCakeViewModel<ProfileViewState>(Loading) { object SaveFailedEvent : OneShotEvent fun saveProfile() { postEvent(SaveFailedEvent) } }
  95. app RainbowCake

  96. app RainbowCake abstract fun provideViewModel(): VM @CallSuper override fun onViewCreated(view:

    View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) viewModel.state.observe(viewLifecycleOwner, Observer { viewState -> viewState?.let { render(it) } }) } abstract fun render(viewState: VS) override fun provideViewModel() = getViewModelFromFactory() override fun render(viewState: ProfileViewState) { when (viewState) { Loading -> { ... } is ProfileLoaded -> { ... } }.exhaustive } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) viewModel.loadProfile() } class ProfileFragment : RainbowCakeFragment<ProfileViewState, ProfileViewModel>() { } } @CallSuper override fun onAttach(context: Context) { super.onAttach(context) viewModel = provideViewModel() } protected lateinit var viewModel: VM abstract class RainbowCakeFragment<VS : Any, VM : RainbowCakeViewModel<VS>> : Fragment() {
  97. app RainbowCake class ProfileFragment : RainbowCakeFragment<ProfileViewState, ProfileViewModel>() { } }

    @CallSuper override fun onAttach(context: Context) { super.onAttach(context) viewModel = provideViewModel() } protected lateinit var viewModel: VM abstract class RainbowCakeFragment<VS : Any, VM : RainbowCakeViewModel<VS>> : Fragment() {
  98. app RainbowCake @CallSuper override fun onAttach(context: Context) { super.onAttach(context) viewModel

    = provideViewModel() viewModel.events.observe(this, Observer { event -> event?.let { onEvent(it) } }) } protected open fun onEvent(event: OneShotEvent) {} } abstract class RainbowCakeFragment<VS : Any, VM : RainbowCakeViewModel<VS>> : Fragment() { protected lateinit var viewModel: VM class ProfileFragment : RainbowCakeFragment<ProfileViewState, ProfileViewModel>() { } override fun onEvent(event: OneShotEvent) { when (event) { is SaveFailedEvent -> showErrorMessage()
  99. app RainbowCake @CallSuper override fun onAttach(context: Context) { super.onAttach(context) viewModel

    = provideViewModel() viewModel.events.observe(this, Observer { event -> event?.let { onEvent(it) } }) } protected open fun onEvent(event: OneShotEvent) {} } abstract class RainbowCakeFragment<VS : Any, VM : RainbowCakeViewModel<VS>> : Fragment() { protected lateinit var viewModel: VM class ProfileFragment : RainbowCakeFragment<ProfileViewState, ProfileViewModel>() { } override fun onEvent(event: OneShotEvent) { when (event) { is SaveFailedEvent -> showErrorMessage()
  100. app RainbowCake @CallSuper override fun onAttach(context: Context) { super.onAttach(context) viewModel

    = provideViewModel() viewModel.events.observe(this, Observer { event -> event?.let { onEvent(it) } }) } protected open fun onEvent(event: OneShotEvent) {} } abstract class RainbowCakeFragment<VS : Any, VM : RainbowCakeViewModel<VS>> : Fragment() { class ProfileFragment : RainbowCakeFragment<ProfileViewState, ProfileViewModel>() { } override fun onEvent(event: OneShotEvent) { when (event) { is SaveFailedEvent -> showErrorMessage() } }
  101. ViewModel Fragment

  102. ViewModel Fragment 1

  103. ViewModel Fragment

  104. ViewModel Fragment

  105. ViewModel Fragment 2

  106. ViewModel Fragment 3

  107. ViewModel Fragment 3

  108. ViewModel Fragment

  109. app RainbowCake class ProfileViewModel : RainbowCakeViewModel<ProfileViewState>(Loading) { object SaveFailedEvent :

    OneShotEvent fun saveProfile() { postEvent(SaveFailedEvent) } } abstract class RainbowCakeViewModel<VS : Any>( initialState: VS ) : ViewModel() { private val _events: MutableLiveData<OneShotEvent> = SingleShotLiveData<OneShotEvent>() val events: LiveData<OneShotEvent> = _events protected fun postEvent(event: OneShotEvent) { _events.postValue(event) } }
  110. app RainbowCake class ProfileViewModel : RainbowCakeViewModel<ProfileViewState>(Loading) { object SaveFailedEvent :

    OneShotEvent fun saveProfile() { postEvent(SaveFailedEvent) } } abstract class RainbowCakeViewModel<VS : Any>( initialState: VS ) : ViewModel() { private val _events: MutableLiveData<OneShotEvent> = QueuedSingleShotLiveData<OneShotEvent>() val events: LiveData<OneShotEvent> = _events protected fun postEvent(event: OneShotEvent) { _events.postValue(event) } } [8]
  111. ViewModel Fragment

  112. ViewModel Fragment 1

  113. ViewModel Fragment 1 2

  114. ViewModel Fragment 1 2

  115. ViewModel Fragment 2

  116. ViewModel Fragment

  117. ViewModel Fragment Fragment Fragment

  118. ViewModel Fragment Fragment Fragment

  119. Fragment Fragment Fragment ViewModel

  120. Fragment Fragment Fragment ViewModel

  121. Fragment Fragment Fragment ViewModel

  122. Fragment Fragment Fragment ViewModel

  123. RainbowCake interface LiveDataCollection<T : Any> { fun observe(owner: LifecycleOwner, observer:

    Observer<T>) } interface MutableLiveDataCollection<T : Any> : LiveDataCollection<T> { fun setValue(value: T?) fun postValue(value: T?) }
  124. RainbowCake class MutableLiveDataCollectionImpl<T : Any>( private val factory: () ->

    MutableLiveData<T> ) : MutableLiveDataCollection<T> { } interface LiveDataCollection<T : Any> { fun observe(owner: LifecycleOwner, observer: Observer<T>) } interface MutableLiveDataCollection<T : Any> : LiveDataCollection<T> { fun setValue(value: T?) fun postValue(value: T?) }
  125. RainbowCake class MutableLiveDataCollectionImpl<T : Any>( private val factory: () ->

    MutableLiveData<T> ) : MutableLiveDataCollection<T> { } interface LiveDataCollection<T : Any> { fun observe(owner: LifecycleOwner, observer: Observer<T>) } interface MutableLiveDataCollection<T : Any> : LiveDataCollection<T> { fun setValue(value: T?) fun postValue(value: T?) }
  126. RainbowCake class MutableLiveDataCollectionImpl<T : Any>( private val factory: () ->

    MutableLiveData<T> ) : MutableLiveDataCollection<T> { private val activeLiveData: MutableSet<MutableLiveData<T>> = mutableSetOf() interface LiveDataCollection<T : Any> { fun observe(owner: LifecycleOwner, observer: Observer<T>) } interface MutableLiveDataCollection<T : Any> : LiveDataCollection<T> { fun setValue(value: T?) fun postValue(value: T?) } }
  127. RainbowCake /* ... */ interface LiveDataCollection<T : Any> { fun

    observe(owner: LifecycleOwner, observer: Observer<T>) } interface MutableLiveDataCollection<T : Any> : LiveDataCollection<T> { fun setValue(value: T?) fun postValue(value: T?) } } class MutableLiveDataCollectionImpl<T : Any>( private val factory: () -> MutableLiveData<T> ) : MutableLiveDataCollection<T> { private val activeLiveData: MutableSet<MutableLiveData<T>> = mutableSetOf() override fun observe(owner: LifecycleOwner, observer: Observer<T>) { } val liveData = factory() activeLiveData += liveData // ...
  128. RainbowCake val liveData = factory() activeLiveData += liveData // ...

    interface LiveDataCollection<T : Any> { fun observe(owner: LifecycleOwner, observer: Observer<T>) } interface MutableLiveDataCollection<T : Any> : LiveDataCollection<T> { fun setValue(value: T?) fun postValue(value: T?) } } override fun setValue(value: T?) { activeLiveData.forEach { it.setValue(value) } } override fun postValue(value: T?) { activeLiveData.forEach { it.postValue(value) } } class MutableLiveDataCollectionImpl<T : Any>( private val factory: () -> MutableLiveData<T> ) : MutableLiveDataCollection<T> { private val activeLiveData: MutableSet<MutableLiveData<T>> = mutableSetOf() override fun observe(owner: LifecycleOwner, observer: Observer<T>) { } /* ... */
  129. RainbowCake abstract class RainbowCakeViewModel<VS : Any>( initialState: VS ) :

    ViewModel() { private val _events: MutableLiveData = QueuedSingleShotLiveData<OneShotEvent>() val events: LiveData<OneShotEvent> = _events protected fun postEvent(event: OneShotEvent) { _events.postValue(event) } } abstract class RainbowCakeFragment<VS : Any, VM : RainbowCakeViewModel<VS>> : Fragment() { override fun onAttach(context: Context) { // ... viewModel.events.observe(this, Observer { event -> event?.let { onEvent(it) } }) } protected open fun onEvent(event: OneShotEvent) {} } <OneShotEvent>
  130. RainbowCake abstract class RainbowCakeViewModel<VS : Any>( initialState: VS ) :

    ViewModel() { private val _events: MutableLiveData = QueuedSingleShotLiveData<OneShotEvent>() val events: LiveData<OneShotEvent> = _events protected fun postEvent(event: OneShotEvent) { _events.postValue(event) } } abstract class RainbowCakeFragment<VS : Any, VM : RainbowCakeViewModel<VS>> : Fragment() { override fun onAttach(context: Context) { // ... viewModel.events.observe(this, Observer { event -> event?.let { onEvent(it) } }) } protected open fun onEvent(event: OneShotEvent) {} } <OneShotEvent>
  131. RainbowCake abstract class RainbowCakeViewModel<VS : Any>( initialState: VS ) :

    ViewModel() { private val _events: MutableLiveDataCollection = MutableLiveDataCollectionImpl(::QueuedSingleShotLiveData) val events: LiveData<OneShotEvent> = _events protected fun postEvent(event: OneShotEvent) { _events.postValue(event) } } abstract class RainbowCakeFragment<VS : Any, VM : RainbowCakeViewModel<VS>> : Fragment() { override fun onAttach(context: Context) { // ... viewModel.events.observe(this, Observer { event -> event?.let { onEvent(it) } }) } protected open fun onEvent(event: OneShotEvent) {} } <OneShotEvent>
  132. RainbowCake abstract class RainbowCakeViewModel<VS : Any>( initialState: VS ) :

    ViewModel() { private val _events: MutableLiveDataCollection = MutableLiveDataCollectionImpl(::QueuedSingleShotLiveData) val events: LiveData<OneShotEvent> = _events protected fun postEvent(event: OneShotEvent) { _events.postValue(event) } } abstract class RainbowCakeFragment<VS : Any, VM : RainbowCakeViewModel<VS>> : Fragment() { override fun onAttach(context: Context) { // ... viewModel.events.observe(this, Observer { event -> event?.let { onEvent(it) } }) } protected open fun onEvent(event: OneShotEvent) {} } <OneShotEvent>
  133. RainbowCake abstract class RainbowCakeViewModel<VS : Any>( initialState: VS ) :

    ViewModel() { private val _events: MutableLiveDataCollection<OneShotEvent> = MutableLiveDataCollectionImpl(::QueuedSingleShotLiveData) val events: LiveDataCollection<OneShotEvent> = _events protected fun postEvent(event: OneShotEvent) { _events.postValue(event) } } abstract class RainbowCakeFragment<VS : Any, VM : RainbowCakeViewModel<VS>> : Fragment() { override fun onAttach(context: Context) { // ... viewModel.events.observe(this, Observer { event -> event?.let { onEvent(it) } }) } protected open fun onEvent(event: OneShotEvent) {} }
  134. RainbowCake abstract class RainbowCakeViewModel<VS : Any>( initialState: VS ) :

    ViewModel() { private val _events: MutableLiveDataCollection<OneShotEvent> = MutableLiveDataCollectionImpl(::QueuedSingleShotLiveData) val events: LiveDataCollection<OneShotEvent> = _events protected fun postEvent(event: OneShotEvent) { _events.postValue(event) } } abstract class RainbowCakeFragment<VS : Any, VM : RainbowCakeViewModel<VS>> : Fragment() { override fun onAttach(context: Context) { // ... viewModel.events.observe(this, Observer { event -> event?.let { onEvent(it) } }) } protected open fun onEvent(event: OneShotEvent) {} }
  135. ViewModel Fragment

  136. ViewModel Fragment 1

  137. RainbowCake abstract class RainbowCakeViewModel<VS : Any>( initialState: VS ) :

    ViewModel() { private val viewEvents: MutableLiveDataCollection<OneShotEvent> = MutableLiveDataCollectionImpl(::ActiveOnlySingleShotLiveData) val events: LiveDataCollection<OneShotEvent> = viewEvents protected fun postEvent(event: OneShotEvent) { viewEvents.postValue(event) } } private val queuedViewEvents: MutableLiveDataCollection<QueuedOneShotEvent> = MutableLiveDataCollectionImpl(::QueuedSingleShotLiveData) val queuedEvents: LiveDataCollection<QueuedOneShotEvent> = queuedViewEvents protected fun postQueuedEvent(event: QueuedOneShotEvent) { queuedViewEvents.postValue(event) }
  138. RainbowCake abstract class RainbowCakeViewModel<VS : Any>( initialState: VS ) :

    ViewModel() { private val viewEvents: MutableLiveDataCollection<OneShotEvent> = MutableLiveDataCollectionImpl(::ActiveOnlySingleShotLiveData) val events: LiveDataCollection<OneShotEvent> = viewEvents protected fun postEvent(event: OneShotEvent) { viewEvents.postValue(event) } } private val queuedViewEvents: MutableLiveDataCollection<QueuedOneShotEvent> = MutableLiveDataCollectionImpl(::QueuedSingleShotLiveData) val queuedEvents: LiveDataCollection<QueuedOneShotEvent> = queuedViewEvents protected fun postQueuedEvent(event: QueuedOneShotEvent) { queuedViewEvents.postValue(event) }
  139. RainbowCake abstract class RainbowCakeViewModel<VS : Any>( initialState: VS ) :

    ViewModel() { private val viewEvents: MutableLiveDataCollection<OneShotEvent> = MutableLiveDataCollectionImpl(::ActiveOnlySingleShotLiveData) val events: LiveDataCollection<OneShotEvent> = viewEvents protected fun postEvent(event: OneShotEvent) { viewEvents.postValue(event) } } private val queuedViewEvents: MutableLiveDataCollection<QueuedOneShotEvent> = MutableLiveDataCollectionImpl(::QueuedSingleShotLiveData) val queuedEvents: LiveDataCollection<QueuedOneShotEvent> = queuedViewEvents protected fun postQueuedEvent(event: QueuedOneShotEvent) { queuedViewEvents.postValue(event) } [9]
  140. RainbowCake abstract class RainbowCakeViewModel<VS : Any>( initialState: VS ) :

    ViewModel() { private val viewEvents: MutableLiveDataCollection<OneShotEvent> = MutableLiveDataCollectionImpl(::ActiveOnlySingleShotLiveData) val events: LiveDataCollection<OneShotEvent> = viewEvents protected fun postEvent(event: OneShotEvent) { viewEvents.postValue(event) } } private val queuedViewEvents: MutableLiveDataCollection<QueuedOneShotEvent> = MutableLiveDataCollectionImpl(::QueuedSingleShotLiveData) val queuedEvents: LiveDataCollection<QueuedOneShotEvent> = queuedViewEvents protected fun postQueuedEvent(event: QueuedOneShotEvent) { queuedViewEvents.postValue(event) }
  141. RainbowCake abstract class RainbowCakeViewModel<VS : Any>( initialState: VS ) :

    ViewModel() { private val viewEvents: MutableLiveDataCollection<OneShotEvent> = MutableLiveDataCollectionImpl(::ActiveOnlySingleShotLiveData) val events: LiveDataCollection<OneShotEvent> = viewEvents protected fun postEvent(event: OneShotEvent) { viewEvents.postValue(event) } } private val queuedViewEvents: MutableLiveDataCollection<QueuedOneShotEvent> = MutableLiveDataCollectionImpl(::QueuedSingleShotLiveData) val queuedEvents: LiveDataCollection<QueuedOneShotEvent> = queuedViewEvents protected fun postQueuedEvent(event: QueuedOneShotEvent) { queuedViewEvents.postValue(event) }
  142. RainbowCake abstract class RainbowCakeViewModel<VS : Any>( initialState: VS ) :

    ViewModel() { private val viewEvents: MutableLiveDataCollection<OneShotEvent> = MutableLiveDataCollectionImpl(::ActiveOnlySingleShotLiveData) val events: LiveDataCollection<OneShotEvent> = viewEvents protected fun postEvent(event: OneShotEvent) { viewEvents.postValue(event) } } private val queuedViewEvents: MutableLiveDataCollection<QueuedOneShotEvent> = MutableLiveDataCollectionImpl(::QueuedSingleShotLiveData) val queuedEvents: LiveDataCollection<QueuedOneShotEvent> = queuedViewEvents protected fun postQueuedEvent(event: QueuedOneShotEvent) { queuedViewEvents.postValue(event) }
  143. RainbowCake abstract class RainbowCakeViewModel<VS : Any>( initialState: VS ) :

    ViewModel() { private val viewEvents: MutableLiveDataCollection<OneShotEvent> = MutableLiveDataCollectionImpl(::ActiveOnlySingleShotLiveData) val events: LiveDataCollection<OneShotEvent> = viewEvents protected fun postEvent(event: OneShotEvent) { viewEvents.postValue(event) } } private val queuedViewEvents: MutableLiveDataCollection<QueuedOneShotEvent> = MutableLiveDataCollectionImpl(::QueuedSingleShotLiveData) val queuedEvents: LiveDataCollection<QueuedOneShotEvent> = queuedViewEvents protected fun postQueuedEvent(event: QueuedOneShotEvent) { queuedViewEvents.postValue(event) }
  144. RainbowCake interface OneShotEvent interface QueuedOneShotEvent : OneShotEvent abstract class RainbowCakeFragment<VS

    : Any, VM : RainbowCakeViewModel<VS>> : Fragment() { override fun onAttach(context: Context) { // ... } viewModel.events.observe(this, Observer { event -> event?.let { onEvent(it) } })
  145. RainbowCake interface OneShotEvent interface QueuedOneShotEvent : OneShotEvent abstract class RainbowCakeFragment<VS

    : Any, VM : RainbowCakeViewModel<VS>> : Fragment() { override fun onAttach(context: Context) { // ... } protected open fun onEvent(event: OneShotEvent) {} } viewModel.events.observe(this, Observer { event -> event?.let { onEvent(it) } })
  146. RainbowCake interface OneShotEvent interface QueuedOneShotEvent : OneShotEvent abstract class RainbowCakeFragment<VS

    : Any, VM : RainbowCakeViewModel<VS>> : Fragment() { override fun onAttach(context: Context) { // ... viewModel.queuedEvents.observe(this, Observer { event -> event?.let { onEvent(it) } }) } protected open fun onEvent(event: OneShotEvent) {} } viewModel.events.observe(this, Observer { event -> event?.let { onEvent(it) } })
  147. ViewModel Fragment Fragment Fragment

  148. ViewModel Fragment Fragment Fragment

  149. ViewModel Fragment

  150. But… these are all hacks!

  151. RainbowCake rainbowcake/rainbowcake

  152. RainbowCake rainbowcake/rainbowcake rainbowcake.dev

  153. RainbowCake rainbowcake/rainbowcake

  154. RainbowCake rainbowcake/rainbowcake

  155. zsmb13 zsmb.co/talks

  156. Handling View State and Events with RainbowCake zsmb.co/talks zsmb13 Márton

    Braun › Built on Jetpack › Single view state › Two event streams › Many LiveData hacks