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.

Marton Braun

June 05, 2020
Tweet

More Decks by Marton Braun

Other Decks in Programming

Transcript

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

    View Slide

  2. RainbowCake
    rainbowcake/rainbowcake

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  8. RainbowCake
    rainbowcake/rainbowcake
    An Android architecture framework
    libraries

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  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

    View Slide

  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]

    View Slide

  14. ItemApi
    ItemDao
    NetworkDataSource
    DiskDataSource
    ShopInteractor
    UserInteractor
    ProfilePresenter CartPresenter
    HomePresenter
    ProfileViewModel CartViewModel
    HomeViewModel
    CartViewState
    ProfileViewState
    HomeViewState
    CartFragment
    ProfileFragment
    HomeFragment

    View Slide

  15. Dao
    Data source
    Interactor
    Presenter
    ViewModel
    ViewState
    Fragment

    View Slide

  16. ViewState
    ViewModel
    Presenter
    Interactor
    Data source
    Dao
    Fragment

    View Slide

  17. ViewModel
    Presenter
    Interactor
    Data source
    Fragment

    View Slide

  18. RainbowCake

    View Slide

  19. Data source
    Interactor
    Presenter
    ViewModel
    Fragment

    View Slide

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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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]

    View Slide

  33. State handling

    View Slide

  34. View Slide

  35. app
    RainbowCake

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  46. app
    RainbowCake
    abstract class RainbowCakeViewModel
    protected val _state: MutableLiveData = MutableLiveData()
    val state: LiveData = _state.distinct()
    }
    class ProfileViewModel : RainbowCakeViewModel()
    : ViewModel() {
    [3]

    View Slide

  47. app
    RainbowCake
    abstract class RainbowCakeViewModel(
    initialState: VS
    )
    protected val _state: MutableLiveData = MutableLiveData()
    val state: LiveData = _state.distinct()
    }
    class ProfileViewModel : RainbowCakeViewModel()
    : ViewModel() {

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  52. RainbowCake
    abstract class RainbowCakeViewModel(
    initialState: VS
    ) : ViewModel() {
    protected val _state: MutableLiveData = MutableLiveData()
    val state: LiveData = _state.distinct()
    init {
    _state.value = initialState
    }
    }

    View Slide

  53. RainbowCake
    protected var viewState: VS
    get() = _state.value!!
    set(value) {
    _state.postValue(value)
    }
    abstract class RainbowCakeViewModel(
    initialState: VS
    ) : ViewModel() {
    }
    val state: LiveData = _state.distinct()
    init {
    _state.value = initialState
    }
    val _state: MutableLiveData = MutableLiveData()
    private

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  62. app
    RainbowCake
    ) : ViewModel() {
    protected var viewState: VS
    get() = _state.value!!
    set(value) {
    _state.postValue(value)
    }
    }
    class ProfileViewModel : RainbowCakeViewModel(Loading) {
    fun loadProfile() {

    View Slide

  63. app
    RainbowCake
    abstract class RainbowCakeFragment : Fragment()
    class ProfileFragment : RainbowCakeFragment()

    View Slide

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

    View Slide

  65. app
    RainbowCake
    abstract class RainbowCakeFragment Fragment() {
    protected lateinit var viewModel VM
    }
    class ProfileFragment : RainbowCakeFragment()
    :
    :

    View Slide

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

    View Slide

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

    View Slide

  68. RainbowCake
    ?
    app
    abstract class RainbowCakeFragment>
    protected lateinit var viewModel: VM
    }
    class ProfileFragment : RainbowCakeFragment()
    : : Fragment(

    View Slide

  69. app
    RainbowCake
    abstract class RainbowCakeFragment>
    protected lateinit var viewModel: VM
    }
    class ProfileFragment : RainbowCakeFragment()
    : Fragment() {

    View Slide

  70. app
    RainbowCake
    abstract class RainbowCakeFragment>
    : Fragment() {
    protected lateinit var viewModel: VM
    }
    class ProfileFragment : RainbowCakeFragment()

    View Slide

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

    View Slide

  72. RainbowCake
    app
    class ProfileFragment : RainbowCakeFragment() {
    }
    abstract fun provideViewModel(): VM
    @CallSuper
    override fun onAttach(context: Context) {
    super.onAttach(context)
    viewModel = provideViewModel()
    }
    override fun provideViewModel() = getViewModelFromFactory()
    abstract class RainbowCakeFragment>
    : Fragment() {
    protected lateinit var viewModel: VM
    }
    [5]

    View Slide

  73. RainbowCake
    app
    class ProfileFragment : RainbowCakeFragment() {
    }
    abstract fun provideViewModel(): VM
    @CallSuper
    override fun onAttach(context: Context) {
    super.onAttach(context)
    viewModel = provideViewModel()
    }
    override fun provideViewModel() = getViewModelFromFactory()
    abstract class RainbowCakeFragment>
    : Fragment() {
    protected lateinit var viewModel: VM
    }

    View Slide

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

    View Slide

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

    View Slide

  76. app
    RainbowCake
    :
    protected lateinit var viewModel: VM
    abstract class RainbowCakeFragment>
    : 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() {
    override fun render(viewState: ProfileViewState) {
    when (viewState) {
    Loading -> { ... }

    View Slide

  77. app
    RainbowCake
    override fun render(viewState: ProfileViewState) {
    when (viewState) {
    Loading -> { ... }
    is ProfileLoaded -> { ... }
    }
    }
    :
    abstract fun render(viewState VS)
    }
    abstract class RainbowCakeFragment>
    : 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() {
    }

    View Slide

  78. app
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    super.onViewCreated(view, savedInstanceState)
    RainbowCake
    abstract class RainbowCakeFragment>
    : 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() {
    }
    .exhaustive
    [2]

    View Slide

  79. app
    override fun render(viewState: ProfileViewState) {
    when (viewState) {
    Loading -> { ... }
    is ProfileLoaded -> { ... }
    }
    }
    class ProfileFragment : RainbowCakeFragment() {
    }
    .exhaustive
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    super.onViewCreated(view, savedInstanceState)
    viewModel.loadProfile()
    }

    View Slide

  80. Event handling

    View Slide

  81. RainbowCake
    app
    abstract class RainbowCakeViewModel(
    initialState: VS
    ) : ViewModel() {
    }
    class ProfileViewModel : RainbowCakeViewModel(Loading)

    View Slide

  82. RainbowCake
    app
    class ProfileViewModel : RainbowCakeViewModel(Loading)
    abstract class RainbowCakeViewModel(
    initialState: VS
    ) : ViewModel() {
    }
    SingleShotLiveData>()
    events: MutableLiveData> =
    val

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  90. RainbowCake
    app
    class ProfileViewModel RainbowCakeViewModel(Loading)
    abstract class RainbowCakeViewModel(
    initialState: VS
    ) : ViewModel() {
    protected val _events: MutableLiveData =
    SingleShotLiveData()
    val events: LiveData = _events
    }
    interface OneShotEvent
    :
    {
    object SaveFailedEvent : OneShotEvent

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  95. app
    RainbowCake

    View Slide

  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() {
    }
    }
    @CallSuper
    override fun onAttach(context: Context) {
    super.onAttach(context)
    viewModel = provideViewModel()
    }
    protected lateinit var viewModel: VM
    abstract class RainbowCakeFragment> : Fragment() {

    View Slide

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

    View Slide

  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>
    : Fragment() {
    protected lateinit var viewModel: VM
    class ProfileFragment : RainbowCakeFragment() {
    }
    override fun onEvent(event: OneShotEvent) {
    when (event) {
    is SaveFailedEvent -> showErrorMessage()

    View Slide

  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>
    : Fragment() {
    protected lateinit var viewModel: VM
    class ProfileFragment : RainbowCakeFragment() {
    }
    override fun onEvent(event: OneShotEvent) {
    when (event) {
    is SaveFailedEvent -> showErrorMessage()

    View Slide

  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>
    : Fragment() {
    class ProfileFragment : RainbowCakeFragment() {
    }
    override fun onEvent(event: OneShotEvent) {
    when (event) {
    is SaveFailedEvent -> showErrorMessage()
    }
    }

    View Slide

  101. ViewModel
    Fragment

    View Slide

  102. ViewModel
    Fragment 1

    View Slide

  103. ViewModel
    Fragment

    View Slide

  104. ViewModel
    Fragment

    View Slide

  105. ViewModel
    Fragment 2

    View Slide

  106. ViewModel
    Fragment 3

    View Slide

  107. ViewModel
    Fragment 3

    View Slide

  108. ViewModel
    Fragment

    View Slide

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

    View Slide

  110. app
    RainbowCake
    class ProfileViewModel : RainbowCakeViewModel(Loading) {
    object SaveFailedEvent : OneShotEvent
    fun saveProfile() {
    postEvent(SaveFailedEvent)
    }
    }
    abstract class RainbowCakeViewModel(
    initialState: VS
    ) : ViewModel() {
    private val _events: MutableLiveData =
    QueuedSingleShotLiveData()
    val events: LiveData = _events
    protected fun postEvent(event: OneShotEvent) {
    _events.postValue(event)
    }
    }
    [8]

    View Slide

  111. ViewModel
    Fragment

    View Slide

  112. ViewModel
    Fragment 1

    View Slide

  113. ViewModel
    Fragment 1 2

    View Slide

  114. ViewModel
    Fragment 1 2

    View Slide

  115. ViewModel
    Fragment 2

    View Slide

  116. ViewModel
    Fragment

    View Slide

  117. ViewModel
    Fragment
    Fragment
    Fragment

    View Slide

  118. ViewModel
    Fragment
    Fragment
    Fragment

    View Slide

  119. Fragment
    Fragment
    Fragment
    ViewModel

    View Slide

  120. Fragment
    Fragment
    Fragment
    ViewModel

    View Slide

  121. Fragment
    Fragment
    Fragment
    ViewModel

    View Slide

  122. Fragment
    Fragment
    Fragment
    ViewModel

    View Slide

  123. RainbowCake
    interface LiveDataCollection {
    fun observe(owner: LifecycleOwner, observer: Observer)
    }
    interface MutableLiveDataCollection : LiveDataCollection {
    fun setValue(value: T?)
    fun postValue(value: T?)
    }

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  128. RainbowCake
    val liveData = factory()
    activeLiveData += liveData
    // ...
    interface LiveDataCollection {
    fun observe(owner: LifecycleOwner, observer: Observer)
    }
    interface MutableLiveDataCollection : LiveDataCollection {
    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(
    private val factory: () -> MutableLiveData
    ) : MutableLiveDataCollection {
    private val activeLiveData: MutableSet> = mutableSetOf()
    override fun observe(owner: LifecycleOwner, observer: Observer) { }
    /* ... */

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  135. ViewModel
    Fragment

    View Slide

  136. ViewModel
    Fragment 1

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  146. RainbowCake
    interface OneShotEvent
    interface QueuedOneShotEvent : OneShotEvent
    abstract class RainbowCakeFragment>
    : 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) }
    })

    View Slide

  147. ViewModel
    Fragment
    Fragment
    Fragment

    View Slide

  148. ViewModel
    Fragment
    Fragment
    Fragment

    View Slide

  149. ViewModel
    Fragment

    View Slide

  150. But… these are all hacks!

    View Slide

  151. RainbowCake
    rainbowcake/rainbowcake

    View Slide

  152. RainbowCake
    rainbowcake/rainbowcake
    rainbowcake.dev

    View Slide

  153. RainbowCake
    rainbowcake/rainbowcake

    View Slide

  154. RainbowCake
    rainbowcake/rainbowcake

    View Slide

  155. zsmb13
    zsmb.co/talks

    View Slide

  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

    View Slide