How to emit distinct values from MutableLiveData? - android

I observed that MutableLiveData triggers onChanged of an observer even if the same object instance is provided to its setValue method.
//Fragment#onCreateView - scenario1
val newValue = "newValue"
mutableLiveData.setValue(newValue) //triggers observer
mutableLiveData.setValue(newValue) //triggers observer
//Fragment#onCreateView - scenario2
val newValue = "newValue"
mutableLiveData.postValue(newValue) //triggers observer
mutableLiveData.postValue(newValue) //does not trigger observer
Is there a way to avoid an observer be notified twice if the same or an equivalent instance is provided to setValue()/postValue()
I tried extending MutableLiveData but that did not work. I could be missing something here
class DistinctLiveData<T> : MutableLiveData<T>() {
private var cached: T? = null
#Synchronized override fun setValue(value: T) {
if(value != cached) {
cached = value
super.setValue(value)
}
}
#Synchronized override fun postValue(value: T) {
if(value != cached) {
cached = value
super.postValue(value)
}
}
}

There is already in API : Transformations.distinctUntilChanged()
distinctUntilChanged
public static LiveData<X> distinctUntilChanged (LiveData<X> source)
Creates a new LiveData object does not emit a value until the source
LiveData value has been changed. The value is considered changed if
equals() yields false.
<<snip remainder>>

You can use the following magic trick to consume "items being the same":
fun <T> LiveData<T>.distinctUntilChanged(): LiveData<T> = MediatorLiveData<T>().also { mediator ->
mediator.addSource(this, object : Observer<T> {
private var isInitialized = false
private var previousValue: T? = null
override fun onChanged(newValue: T?) {
val wasInitialized = isInitialized
if (!isInitialized) {
isInitialized = true
}
if(!wasInitialized || newValue != previousValue) {
previousValue = newValue
mediator.postValue(newValue)
}
}
})
}
If you want to check referential equality, it's !==.
But it has since been added to Transformations.distinctUntilChanged.

If we talk about MutableLiveData, you can create a class and override setValue and then only call through super if new value != old value
class DistinctUntilChangedMutableLiveData<T> : MutableLiveData<T>() {
override fun setValue(value: T?) {
if (value != this.value) {
super.setValue(value)
}
}
}

In my case I have quite complex objects which I have to compare by some fields. For this I've changed EpicPandaForce's answer:
fun <T> LiveData<T>.distinctUntilChanged(compare: T?.(T?) -> Boolean = { this == it }): LiveData<T> = MediatorLiveData<T>().also { mediator ->
mediator.addSource(this) { newValue ->
if(!newValue.compare(value)) {
mediator.postValue(newValue)
}
}
}
By default it uses standard equals method, but if you need - you can change distinction logic

Related

What the difference between observe and wrapper observeEvents (LiveData)?

There is a convenient wrapper that allows you to reduce the boilerplate when you work with LiveData - observeEvents.
open class Event<T>(value: T? = null) {
val liveData = MutableLiveData(value)
protected var hasBeenHandled = false
fun getContentIfNotHandled(): T? {
return if (hasBeenHandled || liveData.value == null) {
null
} else {
hasBeenHandled = true
liveData.value
}
}
companion object {
fun <T> LifecycleOwner.observeEvents(event: Event<T>, body: (T?) -> Unit) {
event.liveData.observe(this) { body(event.getContentIfNotHandled()) }
}
}
}
class MutableEvent<T>(value: T? = null) : Event<T>(value) {
#MainThread
fun fireEvent(event: T) {
hasBeenHandled = false
liveData.value = event
}
#WorkerThread
fun postEvent(event: T) {
hasBeenHandled = false
liveData.postValue(event)
}
}
Next, we can see how to use it.
There is the following sealed class for specific events:
sealed class ProductEvent {
data class AddProduct(val data: SomeProduct) : ProductEvent()
data class RemoveProduct(val productId: String) : ProductEvent()
}
ViewModel code:
private val _productEvents = MutableEvent<ProductEvent>()
val productEvents = _productEvents
private fun addProduct() {
val product: SomeProduct = repository.getProduct()
_productEvents.fireEvent(ProductEvent.AddProduct(product)
}
Activity/Fragment code:
observeEvents(viewModel.productEvents) { event ->
event?.let {
when(event) {
is ProductEvent.AddProduct -> // add product
is ProductEvent.RemoveProduct-> // remove product
}
}
}
Everything works fine, but there is one thing.
For example, when we use registerForActivityResult:
private val result = registerForActivityResult(ActivityResultContracts.StartActivityForResult())
{ result: ActivityResult ->
if (result.resultCode == Activity.RESULT_OK) {
result.data?.getIntExtra(SomeActivity.SOME_RESULT, 0)?.let {
// do work that will call ProductEvent
// viewModel.addProduct() - for example
}
}
}
When SomeActivity finishes and we return here, this code will run before LifecycleOwner will be active and because of that a subscriber will not be called.
There is a solution (lifecycleScope.launchWhenResumed), but the fact is that if we define our LiveData as usual:
// viewModel
private val _product = MutableLiveData<SomeProduct>()
val product = _product
// Activity/Fragment
viewModel.product.observe(lifecycleOwner) {}
then the subscriber will work as expected.
I would like to know what the difference. observeEvents is merely a wrapper that does the same thing, but for some reason works a little differently.

Live data observer triggered twice on fragment created

The issue that I have is not actually bug or big problem. And all works as it should, but nevertheless it annoys me.
In Fragment pbserver:
viewModel.pageNumbersPosition.observe(viewLifecycleOwner) {
if (it!=null) {
SharedPreferenceHelper.pagesNumber = viewModel.pageNumbersArray.value?.get(it)
DLog.d("Set: ${viewModel.pageNumbersArray.value?.get(it)}}")
//Log shows twice as start
}
}
ViewModel:
class MenuViewModel : ViewModel() {
var pageNumbersArray = MutableLiveData(getPageNumbering())
var pageNumbersPosition = MutableLiveData(pageNumbersArray.value?.indexOf(SharedPreferenceHelper.pagesNumber))
private fun getPageNumbering():Array<String> {
val list = mutableListOf<String>()
for (i in 1..25) {
list.add(i.toString())
}
return list.toTypedArray()
}
}
Spinner:
<Spinner
android:id="#+id/spinner"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:entries="#{viewModel.pageNumbersArray}"
android:selectedItemPosition="#={viewModel.pageNumbersPosition}"/>
What happes is viewModel.pageNumbersPosition.observe triggered twice on start. Once from the initiation of the fragment and second time when the spinner sets. This is actually suppose to happen, but I don't like it when Shared Preference sets twice.
I came across a handy class SingleLiveEvent that we can use instead of LiveData in ViewModel class to send only new updates after subscription.
class SingleLiveEvent<T> : MutableLiveData<T>() {
private val pending = AtomicBoolean(false)
override fun observe(owner: LifecycleOwner, observer: Observer<in T>) {
super.observe(owner, Observer<T> { t ->
if (pending.compareAndSet(true, false)) {
observer.onChanged(t)
}
})
}
override fun setValue(t: T?) {
pending.set(true)
super.setValue(t)
}
fun call() {
postValue(null)
}
}
This LiveData extension only calls the observable if there's an explicit call to setValue() or call().
Update, primary constructor with parameter:
class SingleLiveEvent<T>(value: T) : MutableLiveData<T>(value) {...}
You can check if there is equal value in your shared to avoid the double set
if (it!=null) {
viewModel.pageNumbersArray.value?.get(it).let{ value ->
if (SharedPreferenceHelper.pagesNumber != value)
SharedPreferenceHelper.pagesNumber = value
}
}

Kotlin - Return callback event

I have a quick question about Kotlin,
For example I have a class A which have this field:
private val observer: Observer<O> = object : Observer<O> {
override fun onChanged(output: O) {
}
}
Is there any Kotlin way of returning/passing/extending the onChange event (not the value) thru a method?
I don't want to expose the output thru a listener/callback(Java way).
What I'm looking for is to somehow return the onChanged method call, without using a "middle" object/callback
Thanks
when we say return a value, it returns a value back to the callee, in this case, whoever called the onChanged method. This happens in case of synchronous calls.
In this case, onChanged call will be invoked in an asynchronous manner which makes it impossible to simply return a value back to the callee without a callback.
If i correctly understand your question, you can use observer.onChanged as Kotlin Function:
val observerOnChangedFunction = observer.run { ::onChanged }
Than you can invoke this function:
observerOnChangedFunction(instanceOfO)
Usecase: onChanged as var field
class Foo<O> {
var onChanged: (O) -> Unit = { /* default */}
private val observer: Observer<O> = object : Observer<O> {
override fun onChanged(output: O) = onChanged(output)
}
}
fun main() {
val foo = Foo<Int>()
foo.onChanged = { it.toString() }
}
-
Usecase: parameter in constructor as observer
class Foo<O> (
observer: Observer<O>
) {
private val observer: Observer<O> = observer
}
-
Usecase: parameter in constructor as onChanged lambda
class Foo<O> (
onChanged: (O) -> Unit
) {
private val observer: Observer<O> = object : Observer<O> {
override fun onChanged(output: O) = onChanged(output)
}
}

Multiple calls to set LiveData is not observed

I have recently seen a weird issue that is acting as a barrier to my project.
Multiple calls to set the live data value does not invoke the observer in the view.
It seems that only the last value that was set actually invokes the Observer in the view.
Here is the code snippet for a review.
MainActivity.kt
class MainActivity : AppCompatActivity() {
private lateinit var viewModel: MainViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
viewModel = ViewModelProviders.of(this).get(MainViewModelImpl::class.java)
viewModel.state().observe(this, Observer {
onStateChange(it!!)
})
viewModel.fetchFirstThree()
}
private fun onStateChange(state: MainViewModel.State) {
when (state) {
is One -> {
show(state.data)
}
is Two -> {
show(state.data)
}
is Three -> {
show(state.data)
}
}
}
private fun show(data: String) {
Log.d("Response", data)
}
}
MainViewModel.kt
abstract class MainViewModel : ViewModel() {
sealed class State {
data class One(val data: String) : State()
data class Two(val data: String) : State()
data class Three(val data: String) : State()
}
abstract fun state(): LiveData<State>
abstract fun fetchFirstThree()
}
MainViewModelImpl.kt
class MainViewModelImpl : MainViewModel() {
private val stateLiveData: MediatorLiveData<State> = MediatorLiveData()
override fun state(): LiveData<State> = stateLiveData
override fun fetchFirstThree() {
stateLiveData.value = State.One("One")
stateLiveData.value = State.Two("Two")
stateLiveData.value = State.Three("Three")
}
}
Expected output:
Response: One
Response: Two
Response: Three
Actual Output:
Response: Three
As per the output above, the Observer is not being called for the first two values.
I did some science, re-implementing LiveData and MutableLiveData to log out some data.
Check the source code here.
setValue value=Test1
dispatchingValue mDispatchingValue=false mDispatchInvalidated=false
considerNotify
Returned at !observer.active
setValue value=Test2
dispatchingValue mDispatchingValue=false mDispatchInvalidated=false
considerNotify
Returned at !observer.active
setValue value=Test3
dispatchingValue mDispatchingValue=false mDispatchInvalidated=false
considerNotify
Returned at !observer.active
dispatchingValue mDispatchingValue=false mDispatchInvalidated=false
considerNotify
ITEM: Test3
It looks like the observer hasn't reached an active state when you send the initial values.
private void considerNotify(LifecycleBoundObserver observer) {
// <-- Three times it fails here. This means that your observer wasn't ready for any of them.
if (!observer.active) {
return;
}
Once the observer reaches an active state, it sends the last set value.
void activeStateChanged(boolean newActive) {
if (newActive == active) {
return;
}
active = newActive;
boolean wasInactive = LiveData.this.mActiveCount == 0;
LiveData.this.mActiveCount += active ? 1 : -1;
if (wasInactive && active) {
onActive();
}
if (LiveData.this.mActiveCount == 0 && !active) {
onInactive();
}
if (active) {
// <--- At this point you are getting a call to your observer!
dispatchingValue(this);
}
}
I had such issue too.
To resolve it was created custom MutableLiveData, that contains a queue of posted values and will notify observer for each value.
You can use it the same way as usual MutableLiveData.
open class MultipleLiveEvent<T> : MutableLiveData<T>() {
private val mPending = AtomicBoolean(false)
private val values: Queue<T> = LinkedList()
#MainThread
override fun observe(owner: LifecycleOwner, observer: Observer<in T>) {
if (hasActiveObservers()) {
Log.w(this::class.java.name, "Multiple observers registered but only one will be notified of changes.")
}
// Observe the internal MutableLiveData
super.observe(owner, { t: T ->
if (mPending.compareAndSet(true, false)) {
observer.onChanged(t)
//call next value processing if have such
if (values.isNotEmpty())
pollValue()
}
})
}
override fun postValue(value: T) {
values.add(value)
pollValue()
}
private fun pollValue() {
value = values.poll()
}
#MainThread
override fun setValue(t: T?) {
mPending.set(true)
super.setValue(t)
}
/**
* Used for cases where T is Void, to make calls cleaner.
*/
#Suppress("unused")
#MainThread
fun call() {
value = null
}
}
You could use custom LiveData like this:
class ActiveMutableLiveData<T> : MutableLiveData<T>() {
private val values: Queue<T> = LinkedList()
private var isActive: Boolean = false
override fun onActive() {
isActive = true
while (values.isNotEmpty()) {
setValue(values.poll())
}
}
override fun onInactive() {
isActive = false
}
override fun setValue(value: T) {
if (isActive) {
super.setValue(value)
} else {
values.add(value)
}
}
}
FWIW I had the same problem but solved it like this...
I originally had some code similar to this...
private fun updateMonth(month: Int){
updateMonth.value = UpdateMonth(month, getDaysOfMonth(month))
}
updateMonth(1)
updateMonth(2)
updateMonth(3)
I experienced the same problem as described...
But when I made this simple change....
private fun updateMonth(month: Int) {
CoroutineScope(Dispatchers.Main).launch {
updateMonth.value = UpdateMonth(month, getDaysOfMonth(month))
}
}
Presumably, each updateMonth is going onto a different thread now, so all of the updates are observed.
You should call viewModel.fetchFirstThree() after Activity's onStart() method. for example in onResume() method.
Because in LiveData the Observer is wrapped as a LifecycleBoundObserver. The field mActive set to true after onStart().
class LifecycleBoundObserver extends ObserverWrapper implements GenericLifecycleObserver {
#Override
boolean shouldBeActive() {
return mOwner.getLifecycle().getCurrentState().isAtLeast(STARTED);// return true after onStart()
}
#Override
public void onStateChanged(LifecycleOwner source, Lifecycle.Event event) {
if (mOwner.getLifecycle().getCurrentState() == DESTROYED) {
removeObserver(mObserver);
return;
}
activeStateChanged(shouldBeActive());// after onStart() change mActive to true
}
}
When the observer notify the change it calls considerNotify, before onStart it will return at !observer.mActive
private void considerNotify(ObserverWrapper observer) {
if (!observer.mActive) {// called in onCreate() will return here.
return;
}
if (!observer.shouldBeActive()) {
observer.activeStateChanged(false);
return;
}
if (observer.mLastVersion >= mVersion) {
return;
}
observer.mLastVersion = mVersion;
//noinspection unchecked
observer.mObserver.onChanged((T) mData);
}

How to observe PagedList data?

I'm using Paging Library and Android Architecture Components. I simply want to observe pagedlist livedata and update my RecyclerView when there is a change.
I'm observing isLoadingLiveData, isEmptyLiveData and errorLiveData objects which are MediatorLiveData objects created in my ViewModel and observed in my fragment. And also observing resultLiveData which returns the fetched Gist list from remote.
In my ViewModel, I created a PagedList LiveData and whenever it's data changed, I wanted to update isLoadingLiveData, isEmptyLiveData, errorLiveData and PagedListAdapter. Therefore, I defined isLoadingLiveData, isEmptyLiveData, errorLiveData and resultLiveData as MediatorLiveData objects. I added resultLiveData as a source of these objects. So when resultLiveData has changed, these objects' onChanged methods will be called. And resultLiveData is depend on userNameLiveData, so when userNameLiveData has changed, allGistsLiveData will be called and it will fetch the data. For example when the user swipe the list, I'm setting userNameLiveData and doing network call again.
My ViewModel:
private val userNameLiveData = MutableLiveData<String>()
private var gists: LiveData<PagedList<Gist>>? = null
val allGistsLiveData: LiveData<PagedList<Gist>>
get() {
if (null == gists) {
gists = GistModel(NetManager(getApplication()), getApplication()).getYourGists(userNameLiveData.value!!).create(0,
PagedList.Config.Builder()
.setPageSize(PAGED_LIST_PAGE_SIZE)
.setInitialLoadSizeHint(PAGED_LIST_PAGE_SIZE)
.setEnablePlaceholders(PAGED_LIST_ENABLE_PLACEHOLDERS)
.build())
}
return gists!!
}
val resultLiveData = MediatorLiveData<LiveData<PagedList<Gist>>>().apply {
this.addSource(userNameLiveData) {
gists = null
this.value = allGistsLiveData
}
}
val isLoadingLiveData = MediatorLiveData<Boolean>().apply {
this.addSource(resultLiveData) { this.value = false }
}
val isEmptyLiveData = MediatorLiveData<Boolean>().apply {
this.addSource(resultLiveData) { this.value = false }
}
val errorLiveData = MediatorLiveData<Boolean>().apply {
this.addSource(resultLiveData) {
if (it == null) {
this.value = true
}
}
}
fun setUserName(userName: String) {
userNameLiveData.value = userName
}
and my fragment:
override fun onViewCreated(view: View?, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewModel.isEmptyLiveData.observe(this#YourGistsFragment, Observer<Boolean> { isVisible ->
emptyView.visibility = if (isVisible!!) View.VISIBLE else View.GONE
})
viewModel.isLoadingLiveData.observe(this#YourGistsFragment, Observer<Boolean> {
it?.let {
swipeRefreshLayout.isRefreshing = it
}
})
viewModel.errorLiveData.observe(this#YourGistsFragment, Observer<Boolean> {
it?.let {
showSnackBar(context.getString(R.string.unknown_error))
}
})
viewModel.setUserName("melomg")
viewModel.resultLiveData.observe(this#YourGistsFragment, Observer { it -> gistAdapter.setList(it?.value) })
}
override fun onRefresh() {
viewModel.setUserName("melomg")
}
my repository:
fun getYourGists(userName: String): LivePagedListProvider<Int, Gist> {
return remoteDataSource.getYourGists(userName, GitHubApi.getGitHubService(context))
}
my remoteDataSource:
fun getYourGists(username: String, dataProvider: GitHubService): LivePagedListProvider<Int, Gist> {
return object : LivePagedListProvider<Int, Gist>() {
override fun createDataSource(): GistTiledRemoteDataSource<Gist> = object : GistTiledRemoteDataSource<Gist>(username, dataProvider) {
override fun convertToItems(items: ArrayList<Gist>?): ArrayList<Gist>? {
return items
}
}
}
}
I tried to create this solution from this project. But my problem is
resultLiveData has been changing without waiting the network call response and therefore the result of response ignored and my ui is being updated before the data has arrived. Since resultLiveData is changing before request and therefore there is no data yet. Simply how can I observe pagedlist livedata?
Finally, I found a working solution but I don't think it is a best solution. So if you have any ideas please don't hesitate to answer. Here is my solution:
I gave up from wrapping my PagedList data with MediatorLiveData. I still leave my all other live data(isLoadingLiveData, isEmptyLiveData, errorLiveData) as MediatorLiveData but added their source when my gists PagedList LiveData initialized. Here is the code:
private var gists: LiveData<PagedList<Gist>>? = null
private val userNameLiveData = MutableLiveData<String>()
val isLoadingLiveData = MediatorLiveData<Boolean>()
val isEmptyLiveData = MediatorLiveData<Boolean>()
val errorLiveData = MediatorLiveData<ErrorMessage>()
val allGistsLiveData: LiveData<PagedList<Gist>>
get() {
if (gists == null) {
gists = GistModel(NetManager(getApplication()), getApplication()).getYourGists(userNameLiveData.value!!).create(0,
PagedList.Config.Builder()
.setPageSize(Constants.PAGED_LIST_PAGE_SIZE)
.setInitialLoadSizeHint(Constants.PAGED_LIST_PAGE_SIZE)
.setEnablePlaceholders(Constants.PAGED_LIST_ENABLE_PLACEHOLDERS)
.build())
isLoadingLiveData.addSource(gists) { isLoadingLiveData.value = false }
isEmptyLiveData.addSource(gists) {
if (it?.size == 0) {
isEmptyLiveData.value = true
}
}
errorLiveData.addSource(gists) {
if (it == null) {
errorLiveData.value = ErrorMessage(errorCode = ErrorCode.GENERAL_ERROR)
}
}
}
}
return gists!!
}
fun setUserName(userName: String) {
isLoadingLiveData.value = true
userNameLiveData.value = userName
gists = null
allGistsLiveData
}
There is a better way to do this. Look this example:
https://github.com/android/architecture-components-samples/tree/master/PagingWithNetworkSample
The trick is putting inside your DataSource a mutable live data like this:
enum class NetworkState { LOADING, LOADED, FAILURE }
val networkState = MutableLiveData<NetworkState>()
override fun loadInitial(params: LoadInitialParams<String>, callback: LoadInitialCallback<String, OrderService.Resource>) {
networkState.post(NetworkState.LOADING)
ApiClient.listMyObjects(
onSuccess = { list ->
networkState.post(NetworkState.LOADED)
callback.onResult(list)
}
onFailure = {
networkState.post(NetworkState.FAILURE)
}
)
}
Then you can listen to your paged list and network state with:
val myDataSource = MyDataSource.Factory().create()
myDataSource.toLiveData().observe(viewLifeCycle, Observer { pagedList ->
// Update your recycler view
myRecyclerViewAdapter.submitList(pagedList)
})
myDataSource.networkState.observe(viewLifeCycle, Observer { state ->
// Show errors, retry button, progress view etc. according to state
})

Categories

Resources