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

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.

Márton Braun

June 05, 2020
Tweet

More Decks by Márton Braun

Other Decks in Programming

Transcript

  1. 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
  2. 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]
  3. ItemApi ItemDao NetworkDataSource DiskDataSource ShopInteractor UserInteractor ProfilePresenter CartPresenter HomePresenter ProfileViewModel

    CartViewModel HomeViewModel CartViewState ProfileViewState HomeViewState CartFragment ProfileFragment HomeFragment
  4. MVVM View access through an interface Separation of View and

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

    business logic Component between View and Model MVP Separation of View and business logic
  6. 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
  7. 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
  8. 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
  9. 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
  10. 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
  11. MVVM Separation of View and business logic Component between View

    and Model Observable state exposed to the View Data binding* MVI
  12. 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
  13. 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
  14. 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
  15. 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
  16. 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]
  17. app RainbowCake abstract class RainbowCakeViewModel ViewModel() { protected val _state:

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

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

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

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

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

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

    protected val _state: MutableLiveData<VS> = MutableLiveData() val state: LiveData<VS> = _state } class ProfileViewModel : RainbowCakeViewModel<ProfileViewState>()
  24. 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]
  25. app RainbowCake abstract class RainbowCakeViewModel<VS : Any> : ViewModel() {

    protected val _state: MutableLiveData<VS> = MutableLiveData() val state: LiveData<VS> = _state } class ProfileViewModel : RainbowCakeViewModel<ProfileViewState>()
  26. 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]
  27. 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() {
  28. 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>()
  29. 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
  30. 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()) } } }
  31. 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()
  32. 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 } }
  33. 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
  34. 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) } }
  35. 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) } }
  36. 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 = } } }
  37. 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()) } } }
  38. 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()) } } }
  39. 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()) } } }
  40. 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()) } } }
  41. 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]
  42. app RainbowCake ) : ViewModel() { protected var viewState: VS

    get() = _state.value!! set(value) { _state.postValue(value) } } class ProfileViewModel : RainbowCakeViewModel<ProfileViewState>(Loading) { fun loadProfile() {
  43. RainbowCake { protected lateinit var viewModel: ? } app class

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

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

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

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

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

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

    : Fragment() { protected lateinit var viewModel: VM } class ProfileFragment : RainbowCakeFragment<ProfileViewState, ProfileViewModel>()
  50. 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 }
  51. 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]
  52. 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 }
  53. 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 }
  54. 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]
  55. 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 -> { ... }
  56. 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>() { }
  57. 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]
  58. 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() }
  59. RainbowCake app abstract class RainbowCakeViewModel<VS : Any>( initialState: VS )

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

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

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

    Any>( initialState: VS ) : ViewModel() { protected _ val events: LiveData<?> = _events } SingleShotLiveData<?>() val events: MutableLiveData<?> =
  63. 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 }
  64. 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
  65. 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
  66. RainbowCake app class ProfileViewModel : RainbowCakeViewModel<ProfileViewState>(Loading) abstract class RainbowCakeViewModel<VS :

    Any>( initialState: VS ) : ViewModel() { protected val _events: MutableLiveData<?> = SingleShotLiveData<?>() val events: LiveData<?> = _events }
  67. 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
  68. 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
  69. 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
  70. 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
  71. 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 } } :
  72. 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) } }
  73. 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() {
  74. 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() {
  75. 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()
  76. 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()
  77. 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() } }
  78. 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) } }
  79. 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]
  80. 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?) }
  81. 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?) }
  82. 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?) }
  83. 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?) } }
  84. 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 // ...
  85. 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>) { } /* ... */
  86. 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>
  87. 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>
  88. 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>
  89. 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>
  90. 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) {} }
  91. 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) {} }
  92. 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) }
  93. 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) }
  94. 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]
  95. 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) }
  96. 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) }
  97. 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) }
  98. 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) }
  99. 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) } })
  100. 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) } })
  101. 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) } })
  102. 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