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 full-size slide

  2. RainbowCake
    rainbowcake/rainbowcake

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  8. RainbowCake
    rainbowcake/rainbowcake
    An Android architecture framework
    libraries

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size 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 full-size 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 full-size slide

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

    View full-size slide

  15. Dao
    Data source
    Interactor
    Presenter
    ViewModel
    ViewState
    Fragment

    View full-size slide

  16. ViewState
    ViewModel
    Presenter
    Interactor
    Data source
    Dao
    Fragment

    View full-size slide

  17. ViewModel
    Presenter
    Interactor
    Data source
    Fragment

    View full-size slide

  18. Data source
    Interactor
    Presenter
    ViewModel
    Fragment

    View full-size slide

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

    View full-size slide

  20. 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 full-size 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
    Component between
    View and Model

    View full-size slide

  22. 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 full-size slide

  23. 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 full-size slide

  24. 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 full-size slide

  25. 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 full-size slide

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

    View full-size slide

  27. 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 full-size 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
    Reactive data streams

    View full-size 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
    Input events as objects

    View full-size 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
    Reducer

    View full-size 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
    [1]

    View full-size slide

  32. State handling

    View full-size slide

  33. app
    RainbowCake

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  42. 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 full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  46. 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 full-size slide

  47. 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 full-size 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(Loading) {
    fun loadProfile() {
    viewModelScope.launch {
    _state.value = Loading
    _state.value = ProfileLoaded(loadUserName())
    }
    }
    }

    View full-size 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 =
    _state.value =
    }
    }
    }
    _state.value!!.copy(isLoading = true)
    _state.value!!.copy(name = )
    loadUserName()

    View full-size slide

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

    View full-size slide

  51. 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 full-size slide

  52. 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 full-size slide

  53. 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 full-size slide

  54. 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 full-size slide

  55. 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 full-size slide

  56. 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 full-size slide

  57. 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 full-size 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 full-size slide

  59. 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 full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  69. 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 full-size slide

  70. 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 full-size slide

  71. 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 full-size slide

  72. 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 full-size slide

  73. 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 full-size slide

  74. 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 full-size slide

  75. 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 full-size slide

  76. 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 full-size slide

  77. 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 full-size slide

  78. Event handling

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  84. 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 full-size slide

  85. 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 full-size slide

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

    View full-size slide

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

    View full-size slide

  88. 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 full-size slide

  89. 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 full-size slide

  90. 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 full-size slide

  91. 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 full-size slide

  92. 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 full-size slide

  93. app
    RainbowCake

    View full-size slide

  94. 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 full-size slide

  95. 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 full-size slide

  96. 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 full-size slide

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

    View full-size slide

  99. ViewModel
    Fragment

    View full-size slide

  100. ViewModel
    Fragment 1

    View full-size slide

  101. ViewModel
    Fragment

    View full-size slide

  102. ViewModel
    Fragment

    View full-size slide

  103. ViewModel
    Fragment 2

    View full-size slide

  104. ViewModel
    Fragment 3

    View full-size slide

  105. ViewModel
    Fragment 3

    View full-size slide

  106. ViewModel
    Fragment

    View full-size slide

  107. 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 full-size slide

  108. 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 full-size slide

  109. ViewModel
    Fragment

    View full-size slide

  110. ViewModel
    Fragment 1

    View full-size slide

  111. ViewModel
    Fragment 1 2

    View full-size slide

  112. ViewModel
    Fragment 1 2

    View full-size slide

  113. ViewModel
    Fragment 2

    View full-size slide

  114. ViewModel
    Fragment

    View full-size slide

  115. ViewModel
    Fragment
    Fragment
    Fragment

    View full-size slide

  116. ViewModel
    Fragment
    Fragment
    Fragment

    View full-size slide

  117. Fragment
    Fragment
    Fragment
    ViewModel

    View full-size slide

  118. Fragment
    Fragment
    Fragment
    ViewModel

    View full-size slide

  119. Fragment
    Fragment
    Fragment
    ViewModel

    View full-size slide

  120. Fragment
    Fragment
    Fragment
    ViewModel

    View full-size slide

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

    View full-size slide

  122. 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 full-size slide

  123. 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 full-size slide

  124. 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 full-size slide

  125. 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 full-size slide

  126. 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 full-size slide

  127. 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 full-size slide

  128. 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 full-size slide

  129. 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 full-size slide

  130. 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 full-size slide

  131. 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 full-size slide

  132. 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 full-size slide

  133. ViewModel
    Fragment

    View full-size slide

  134. ViewModel
    Fragment 1

    View full-size slide

  135. 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 full-size slide

  136. 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 full-size 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)
    }
    [9]

    View full-size 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 full-size 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)
    }

    View full-size 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 full-size 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 full-size slide

  142. 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 full-size slide

  143. 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 full-size slide

  144. 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 full-size slide

  145. ViewModel
    Fragment
    Fragment
    Fragment

    View full-size slide

  146. ViewModel
    Fragment
    Fragment
    Fragment

    View full-size slide

  147. ViewModel
    Fragment

    View full-size slide

  148. But… these are all hacks!

    View full-size slide

  149. RainbowCake
    rainbowcake/rainbowcake

    View full-size slide

  150. RainbowCake
    rainbowcake/rainbowcake
    rainbowcake.dev

    View full-size slide

  151. RainbowCake
    rainbowcake/rainbowcake

    View full-size slide

  152. RainbowCake
    rainbowcake/rainbowcake

    View full-size slide

  153. zsmb13
    zsmb.co/talks

    View full-size slide

  154. 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 full-size slide