Exploration and attempt of LiveData non sticky message

LiveData supports sticky messages by default (for what is sticky messages, please move to another article: Correct use of LiveData, posture and anti pattern ), how to implement non sticky messages through LiveData Guan Bo On the basis of, this paper analyzes several tried schemes, as well as their respective advantages and disadvantages

Pose 1: reset the value of LiveData

Add a judgment in the observer to update the UI logic to respond when the LiveData value meets a certain condition, and then provide a method to reset the LiveData value. After reset, the condition in the observer is judged as fasle, so the purpose of not updating the UI can be achieved

Sample code

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

Defects:

  1. Some logic judgment codes need to be added to the observer, which does not conform to the concise MVVM mode (too much logic processing should not be done in the View layer)
  2. Manual reset is required, which is not elegant enough. Once you forget to reset, it is easy to cause problems

Pose 2: use SingleLiveEvent

SingleLiveEvent It is the LiveData encapsulated in the official sample. It can realize that only one event is 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() {
        setValue(null)
    }

    companion object {
        private const val TAG = "SingleLiveEvent"
    }
}
Copy code

Sample code

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

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

viewModel.billLiveEvent.observe(this, Observer {
    Log.d("sample", "Arrival ${it} element")
})

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

btn_wait.setOnClickListener {
    viewModel.billLiveEvent.observe(this, Observer {
        Log.d("sample", "Arrival ${it} element")
    })
}

// The above code is in onCreate() of Activity
// When the Activity is started, it will output the log - > receipt of 100 yuan (no implementation does not receive events before observe())
// Click btn and output - > 200 yuan received
// Click btn_wait has no output (the event is consumed only once)
Copy code

Defects:

  1. Since onChange() will only call back once after setValue(), if there are multiple observers, only one can receive the callback, and it is impossible to guarantee which observer will be called back (the life cycle of each observer is different, and the timing of observe() is also different)
  2. Events sent before observe will still be received, which does not solve the problem

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(): T? {
        return if (hasBeenConsumed) {
            null
        } else {
            hasBeenConsumed = true
            content
        }
    }

    /**
     * Returns the content, even if it's already been handled.
     */
    fun peek(): T = content
}
Copy 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 code
viewModel.payForEvent("100")

viewModel.billEvent.observe(this, Observer {
		it.consumed()?.let {
			Log.d("sample", "Arrival ${it} element")
		}
})

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

btn_wait.setOnClickListener {
	  viewModel.billEvent.observe(this, Observer {
			it.consumed()?.let {
				Log.d("sample", "Arrival ${it} element")
			}
		})
}

// The above code is in onCreate() of Activity
// When the Activity is started, it will output the log - > receipt of 100 yuan (no implementation does not receive events before observe())
// Click btn and output - > 200 yuan received
// Click btn_wait has no output (the event is consumed only once)
Copy code

The benefits of this approach are:

  • onChanged() will call back every time, but whether to process data depends on observer: consumed() does not return consumed messages, and peek() can return consumed data

Defects:

  1. Like gesture 2, the data before observe() will still be monitored, which does not solve the problem
  2. Although multiple observers can be added and peek() can be used to obtain data, it is still impossible for multiple observers to receive events only once

Pose 4: support multiple observer s and only accept messages after observe()

Can refer to Idea and scheme of event bus based on LiveData

LiveData doesn't have to be used

We use various workaround methods to make LiveData support sticky messages. Only the last of the above solutions can solve the problem. However, the author does not recommend using this way to bypass the restrictions 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: Correct use of LiveData, posture and anti pattern ), there are many excellent open source libraries available for the event bus scenario: EventBus, RxBus, etc. can be used for our reference.

Another article Guan Bo It was also mentioned that if there are some mature schemes in our 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 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 may mention that the existing EventBus or RxBus do not have life cycle awareness and cannot automatically unbind and listen when the life cycle is destroyed. LiveData has this ability, so they want to use LiveData to implement the event bus. In fact, we can change our thinking here: add Lifecycle awareness to callback or EventBus, so as to realize automatic unbinding. This method is more friendly than modifying LiveData by hack. For details, please refer to another article: Customize life cycle and realize life cycle awareness

Related articles

Using Architecture Component to realize the correct posture of MVVM

Correct use of LiveData, posture and anti pattern

Customize life cycle and realize life cycle awareness

Added by sulin on Thu, 09 Dec 2021 06:54:34 +0200