Address: android. Jlelse. Eu/sending – eve…

In MVVM programming, a Toast or snack bar is displayed by sending a message to an Activity/fragment from the ViewModel. The traditional approach has its drawbacks.

Now we want to display a Toast. The ViewModel decides what information to display and when, and the View does the actual display.

Incorrect message handling

Use interfaces or other callbacks

Implement the interface by defining an Activity/Fragment interface. When you need to pass a message, you just call the methods in the interface. The way is simple and crude ~~~, but it is the most stupid choice. In MVVM, we no longer communicate with the ViewModel through an interface, and the ViewModel should not hold a reference to the View.

Send messages using LiveData

The Activity/Fragment listens to the LiveData for message processing by putting information in the LiveData. This approach is simple and effective, but it has some minor drawbacks.

class MyViewModel() : ViewModel(){
    val networkError = MutableLiveData<String>()
    
}

//inside activity
viewModel.networkError.observe(this, Observer { 
    showToast(it)
})
Copy the code

Such code does not meet our requirements in some cases.

  • When the Activity/Fragment is destroyed, the ViewModel is not destroyed. The message is repeated when the horizontal screen is switched (perhaps we have already processed the message several times). The situation is even worse if we listen after onViewCreated. Every time onDestroryView is redrawn after the view has been destroyed, it will be prompted with a message.
  • In some cases, there are multiple fragments that use the same ViewModel. When message notification is performed, all interfaces may process messages. Obviously, we only need to process messages in a fixed listener

Use LiveData with only one notification

There are some LiveData descendants that implement notifying listeners only once. Using this approach can effectively solve the first problem in the previous solution. I believe many people already use this method in their actual work. But when we run into the second problem, we can’t tell which fragment is listening for the message.

Correct message processing posture

LiveData has life awareness and notifies messages only when the View is active. We use a layer of encapsulation to enable us to only notify messages once while taking full advantage of LiveData.

open class Event<out T>(private val content: T) {

    var hasBeenHandled = false
        private set // Allow external read but not write

    /**
     * Returns the content and prevents its use again.
     */
    fun getContentIfNotHandledOrReturnNull(): T? {
        return if (hasBeenHandled) {
            null
        } else {
            hasBeenHandled = true
            content
        }
    }

    /**
     * Returns the content, even if it's already been handled. */ fun peekContent(): T = content } class MyViewModel() : ViewModel(){ val networkError = MutableLiveData
      
       >() } viewModel.networkError.observe(this, Observer { event -> event?.getContentIfNotHandledOrReturnNull()?.let { showToast(it) } })
      Copy the code

Through explicit processing of the message, all listeners may fetch data multiple times (the Activity/Fragment may be destroyed and rebuilt), but the Event Event is only processed by the first observer to receive the message.