LiveData supports sticky messages by default (for what is sticky messages, please refer to my other article: Proper Use posture and Anti-patterns of LiveData). How to implement non-sticky messages through LiveData? Based on the official blog, this article will analyze several attempted solutions, as well as their advantages and disadvantages

Position 1: Resets the value of LiveData

Add a judgment in the Observer that updates the UI logic when the LiveData value meets a certain condition, and then provide a method to reset the LiveData value. After the reset, the observer will judge the condition as Fasle, So you don’t have to update the UI

The sample code

moneyReceivedViewModel.billLiveData.observe(this, Observer {
	if(it ! =null) {
		Toast.makeText(this."To the account$itYuan", Toast.LENGTH_SHORT).show()
	}
})
Copy the code
class MoneyReceivedViewModel : ViewModel {
	private val _billLiveData = MutableLiveData<String>()
	val billLiveData: LiveData<String> = _billLiveData
	
	// Reset LiveData before observe and after show Toast
	fun reset(a) {
		_billLiveData.value = null}}Copy the code

Defect:

  1. You need to add some logic judgment code to the Observer, which is not consistent with the concise MVVM pattern (you should not do too much logic in the View layer)
  2. Manual reset is not elegant and can cause problems if forgotten

Position 2: Use SingleLiveEvent

SingleLiveEvent is the LiveData encapsulated in the official sample, which can realize that an event can only be consumed. The implementation principle is also very simple

class SingleLiveEvent<T> : MutableLiveData<T>() {
    private val mPending: AtomicBoolean = AtomicBoolean(false)

    @MainThread
    override fun observe(owner: LifecycleOwner, observer: Observer<in T>) {
        if (hasActiveObservers()) {
            Log.w(
                TAG,
                "Multiple observers registered but only one will be notified of changes.")}// Observe the internal MutableLiveData
        super.observe(owner, object: Observer<T? > {override fun onChanged(t: T?). {
                if (mPending.compareAndSet(true.false)) {
                    observer.onChanged(t)
                }
            }
        })
    }

    @MainThread
    override fun setValue(t: T?). {
        mPending.set(true)
        super.setValue(t)
    }

    /** * Used for cases where T is Void, to make calls cleaner. */
    @MainThread
    fun call(a) {
        setValue(null)}companion object {
        private const val TAG = "SingleLiveEvent"}}Copy the code

The sample code

class MoneyReceivedViewModel : ViewModel() {
    val billLiveEvent = SingleLiveEvent<String>()

    fun payForLiveEvent(money: String) {
        billLiveEvent.value = money
    }
}
Copy the code
viewModel.payForLiveEvent("100")

viewModel.billLiveEvent.observe(this, Observer {
    Log.d("sample"."To the account${it}Yuan")
})

btn.setOnClickListener {
    viewModel.payForLiveEvent("200")
}

btn_wait.setOnClickListener {
    viewModel.billLiveEvent.observe(this, Observer {
        Log.d("sample"."To the account${it}Yuan")})}// The above code is in the Activity's onCreate()
// When the Activity starts, the log will be output -> receive 100 yuan (did not implement do not receive the event before observe())
// Click BTN and output -> 200 yuan to account
// No output from btn_wait
Copy the code

Defect:

  1. Since onChange() will only be called back once after setValue(), only one observer, if any, will receive the callback, and there is no guarantee which observer will be called back (each observer life cycle is different, Observe () The timing is different.
  2. Events sent before Observe will still be received without resolving the issue

Pose 3: LiveData wraps an Event

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

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

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

    /** * Returns the content, even if it's already been handled. */
    fun peek(a): T = content
}
Copy the code
class MoneyReceivedViewModel : ViewModel() {
    private val _billEvent = MutableLiveData<Event<String>>()

    val billEvent: LiveData<Event<String>>
        get() = _billEvent

    fun payForEvent(msg: String) {
        _billEvent.value = Event(msg)
    }
}
Copy the code
viewModel.payForEvent("100")

viewModel.billEvent.observe(this, Observer { it.consumed()? .let { Log.d("sample"."To the account${it}Yuan")
		}
})

btn.setOnClickListener {
    viewModel.payForEvent("200")
}

btn_wait.setOnClickListener {
	  viewModel.billEvent.observe(this, Observer { it.consumed()? .let { Log.d("sample"."To the account${it}Yuan")}}}// The above code is in the Activity's onCreate()
// When the Activity starts, the log will be output -> receive 100 yuan (did not implement do not receive the event before observe())
// Click BTN and output -> 200 yuan to account
// No output from btn_wait
Copy the code

The benefits of this approach are:

  • OnChanged () calls back each time, but whether to process the data depends on observer: Consumed () not returning messages that have been consumed, peek() can return data that has been consumed

Defect:

  1. As with posture 2, observe() ‘s previous data is monitored, and the problem is not resolved
  2. While it is possible to add multiple observers and use peek() to get data, there is no way that multiple observers can receive an event once

Position 4: Support multiple Observers and only accept messages after Observe ()

You can refer to the idea and scheme of realizing event bus based on LiveData

LiveData doesn’t have to be used

We used various workaround approaches to make LiveData support sticky messages, and only the last of these solutions solved the problem. However, I do not recommend using such a way to bypass the limitations of LiveData and break the original design of LiveData, which will make LiveData more difficult to understand

We don’t have to use LiveData, LiveData has its own usage scenarios (specific steps: proper use of LiveData posture and anti-patterns), and there are many excellent open source libraries available for the EventBus scenario: EventBus, RxBus, etc.

Another official blog also mentioned that if we already have some mature solutions in the project, we don’t have to use LiveData

LiveData and RxJava Finally, Let’s address the elephant in the room. LiveData was designed to allow the View to observe the ViewModel. Definitely use it for this! Even if you already use Rx, you can communicate both with LiveDataReactiveStreams*. If you want to use LiveData beyond the presentation layer, you might find that MediatorLiveData does not have a toolkit to combine and operate on streams of data like RxJava offers. However, Rx comes with a steep learning curve. A combination of LiveData transformations (and Kotlin magic) might be enough for your case but if you (and your team) already invested in learning RxJava, You probably don’t need LiveData. *If you use auto-dispose, using LiveData for this would be redundant.

Some people might mention that the existing EventBus or RxBus systems don’t have lifecycle awareness and can’t automatically unbind listeners when the lifecycle is destroyed. LiveData has this capability, so we want to use LiveData to implement the EventBus. Lifecycle awareness can be added to callback or EventBus so that it can be automatically unbundled. This will be more user-friendly than hacking LiveData. For details, please refer to my other article: Customize the lifecycle and implement lifecycle awareness


Related articles

Use the Architecture Component to implement the proper posture of the MVVM

Proper use of LiveData posture and anti-patterns

Customize the lifecycle and implement lifecycle awareness