'SingleLiveEvent post, if called multiple times, then only the last event is dispatched (but I need all events in my view)
I'm using SingleLiveEvent to communicate my ViewModel and my Activity. Something like that (pseudocode):
class MyActivity: BaseActivity{
fun onCreate(){
//Init viewmodel and so on
viewModel.commands.observe(this, { command ->
logger.debug("Command received","------>>>>>>>"+command.javaClass.simpleName)
processCommand(command)
})
}
}
And my ViewModel is something like:
class MyViewModel(application: Application) : BaseAndroidViewModel(application) {
val command: SingleLiveEvent<CustomCommands> = SingleLiveEvent()
init{
loadOneThing()
command.postValue(CustomCommands.MessageCommand("one thing loaded"))
loadAnotherThing()
command.postValue(CustomCommands.MessageCommand("another thing loaded"))
}
}
The problem that I'm having, is that the Activity is receiving only the last command, and that is per design. SingleLiveEvent is a Child class from LiveData, and the documentation says the following for the method postValue:
* If you called this method multiple times before a main thread executed a posted task, only * the last value would be dispatched.
Interestingly, if I set a breakpoint on the line that posts the commands, the emulator/device/main thread has time enough to process the first command, and the second command is sent too. But when executing the app without breakpoints, if the tasks that the viewmodel does between commands are done very fast (no rest requests or things like that, but some calculations), the main thread does not have time enough to finish the first command, and the second command is ignored.
But I really need the View to receive all events/commands that the ViewModel sends.
I suppose the SingleLiveEvent is not the right tool for that use case, nor is LiveData, because of the problem of already consumed events being resent when the device is rotated and so on.
Somebody knows a better approach to do this?
Thanks in advance!
Solution 1:[1]
I have faced same problem today. I'm also using SingleLiveEvent for commands/event. I have solved this problem using
commands.value = event instead of commands.postValue(event). Then I wonder why it behaving like that. I found this article. In the article,
But for postValue, the value will be updated twice and the number of times the observers will receive the notification depends on the execution of the main thread. For example, if the postValue is called 4 times before the execution of the main thread, then the observer will receive the notification only once and that too with the latest updated data because the notification to be sent is scheduled to be executed on the main thread. So, if you are calling the postValue method a number of times before the execution of the main thread, then the value that is passed lastly i.e. the latest value will be dispatched to the main thread and rest of the values will be discarded.
I hope it help someone that faced same problem.
Solution 2:[2]
have you tried using EventObserver?
/**
* Used as a wrapper for data that is exposed via a LiveData that represents an event.
*/
open class Event<out T>(private val content: T) {
@Suppress("MemberVisibilityCanBePrivate")
var hasBeenHandled = false
private set // Allow external read but not write
/**
* Returns the content and prevents its use again.
*/
fun getContentIfNotHandled(): T? {
return if (hasBeenHandled) {
null
} else {
hasBeenHandled = true
content
}
}
/**
* Returns the content, even if it's already been handled.
*/
fun peekContent(): T = content
}
/**
* An [Observer] for [Event]s, simplifying the pattern of checking if the [Event]'s content has
* already been handled.
*
* [onEventUnhandledContent] is *only* called if the [Event]'s contents has not been handled.
*/
class EventObserver<T>(private val onEventUnhandledContent: (T) -> Unit) : Observer<Event<T>> {
override fun onChanged(event: Event<T>?) {
event?.getContentIfNotHandled()?.let {
onEventUnhandledContent(it)
}
}
}
Use it with live data
val someEvent: MutableLiveData<Event<Unit>>= MutableLiveData()
when you need to some event
fun someEventOccured(){
someEvent.value = Event(Unit)
}
Fragment file, observe the Event
viewModel.someEvent.observe(this, EventObserver {
//somecode
})
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | Scoto |
| Solution 2 | Kashif Mehmood |
