This is one of series of Simple Android Architecture. and you can check sample code there.

This article is about Livedata and Event

When you use MVVM, you would find that there are two cases of data

  1. Just data which is shown on layout
  2. The event which should be called one time even if Activity/Fragment is recreated. like toast, dialog, starting activity.

Someone says that viewLifecycleOwner solved the issue, but In my understanding it just solved the memory leak.

And the two cases are difficult to be merged to one case, as the purpose are different.

Solutions

Then how to handle the event?

  1. Event class used in Android architecture-samples
  2. similar to MVP way. set Activity/Fragment on ViewModel by some interface.

Deep Understanding

Let’s look deep into two ways

  1. Event
    • Pros : It’s lifecycle-aware. so, you don’t need to worry if event is called when Activity/Fragment’s is not running.
    • Cons : if the data is shown on layout and also need to handle event, the readability not that good.(use peekContent() in the sample below)
    • Cons : if use with Resource, too much of depth of generic type is required
      MutableLiveData<Event<Resource>>().
class SampleViewModel : ViewModel() {
    val toastText = MutableLiveData<Event<String>>()
    fun onClick() {
        toastText.value = "aaa"
    }
}
class SampleFragment : Fragment() {
    onActivityCreated() {
        viewModel.toastText.observe(viewLifecycleOwner) {
            toast(it)
        }
    }
}
<TextView   
    android:text="@{model.toastText.peekContent()}"  
/>
  1. Activity/Fragment on ViewModel
    Pros : handle multiple parameter easily
    Cons : need to set Activity/Fragment on created.
    Cons : lifecycle un-aware.
    Cons : if event and layout both is required. need to handle separately like the sample below
class SampleFragment : Fragment(), SampleUi {
    onCreate() {
        viewModel.ui = WeakReference(this)
    }
    override fun goToNextPage(val text: String) {
        toast(text)
    }
}
interface SampleUi {
    fun showToast(val test: String)
}
class SampleViewModel : ViewModel() {
    lateinit var ui: SampleUi
    val toastText = MutableLiveData<String>()
    fun onClick() {
        val TEXT = "test"
        toastText.value = TEXT
        ui.get()?.showToast(TEXT)
    }
}
<TextView   
    android:text="@{model.toastText}"  
/>

Suggestion

There are merits and demerits on both way.

In my opinion Event has more merits, but still has demerits.
so, I focused on how to reduce demerits of Event class

And, I suggest the below

class SampleViewModel : ViewModel() {
    val toastText = LiveObject<String>()
    fun onClick() {
        toastText.value = "aaa"
    }
}
class SampleFragment : Fragment() {
    onActivityCreated() {
        viewModel.toastText.observeEvent {
            toast(it)
        }
    }
}
<TextView   
    android:text="@{model.toastText}"  
/>

I used custom LiveObject class.

Explanation

When you define LiveData field, you have to consider the below

  1. LiveData, MutableLiveData, MediatorLiveData (3 cases)
  2. whether it’s Event or not. (2 cases)
  3. whether it’s Resource or not (2 cases)

Total, there are 12 cases to consider.
Sometimes, requirement is changed, and you have to change the type again and again.

Explanation for 1.

Normally LiveData is used in view side not to change MutableLiveData’s value.

but, mostly, we don’t mistake it. and we can verify by test code.

So, I would like to use just one class for LiveData, MutableLiveData, MediatorLiveData

Explanation for 2.

There is a data which should be shown on UI. Designer decide to show it by TextView in layout.
After that, it’s changed to AlertDialog.

In this case, the data is not changed, but we have to change MutableLiveData to MutableLiveData<Event>

Don’t you think it’s strange? even when data is not changed, but we have to change ViewModel.

so, I wrap LiveData class with custom class. and then view side decide if it’s event or not by the below way

viewModel.toastText.observe {  
    txt_toast.text = it  
}
viewModel.toastText.observeEvent {  
    toast(it)     
}

When it’s event, use observeEvent(), if it’s not event, use observe()
isn’t it simpler and also reasonable that view side decide event or not?

Explanation for 3.

we use Resource custom class for data including state of loading, success, fail.
but, when you define LiveData for it, it’s terrible like

MutableLiveData<Event<Resource<String>>>>

So, I would like to suggest

LiveResource<String>
typealias LiveResource<T> = LiveObject<Resource<T>>

You don’t need to consider it’s Mutable or not, Event or not.

Just use these

LiveResource<String>  
LiveObject<String>

All the 12 cases get shorten to 2 cases. now It can be manageable.

You can check sample and library code here

Happy coding :)