I have a Fragment
, with a dynamic number of a custom views, consisting in an EditText
and a Button
. What I do is that every time the user types a price in the EditText
and clicks the Button
, I make an API request through a ViewModel, and my Fragment
observes the LiveData
in the ViewModel
.
So far so good, when I use the first custom view. The problem comes on the second one (and the third), because the onChanged()
method is apparently called even tho the data has not changed, and the second and the third custom views are listening to that data, so they change when they are NOT the ones triggering the data change (they receive the data change from the first one).
When the user clicks on the Button
, the way I observe and fetch the price is this:
val observer = Observer<NetworkViewState> { networkViewState ->
processResponse(networkViewState, moneySpent, coin, date)
}
boardingHistoricalPriceViewModel.coinDayAveragePrice.observe(this, observer)
boardingHistoricalPriceViewModel.getDayAveragePrice(coin.symbol,
addedCoinDatePriceView.selectedSpinnerItem, dateInMillis)
and what is happening is that the method processResponse
gets called when the second custom view triggered the API request, but the result I receive is the one that coinDayAveragePrice
has before the API response arrives (this is the value after the first API response from the first custom view has arrived).
This is part of my ViewModel
:
val coinDayAveragePrice: MutableLiveData<NetworkViewState> = MutableLiveData()
fun getDayAveragePrice(symbol: String, currency: String, dateInMillis: Long) {
coinRepository
.getDayAverage(symbol, currency, "MidHighLow", dateInMillis)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.doOnSubscribe { coinDayAveragePrice.postValue(NetworkViewState.Loading()) }
.subscribeBy(onSuccess = {
coinDayAveragePrice.postValue(NetworkViewState.Success(it))
}, onError = { throwable ->
coinDayAveragePrice.postValue(NetworkViewState.Error(throwable.localizedMessage))
})
}
NetworkViewState
is just a sealed class
meant as a wrapper for a response of an API request:
sealed class NetworkViewState {
class Loading : NetworkViewState()
class Success<out T>(val item: T) : NetworkViewState()
class Error(val errorMessage: String?) : NetworkViewState()
}
I have also tried to unsubscribe or to set the coinDayAveragePrice
to null, but still I have the same problem.
Thanks a lot in advance!
So, without seeing your ViewModel
, it's hard to be sure exactly what the problem is, but I think it's what I indicated in my comment. In that case, one solution is to use a different kind of LiveData
. I got this basic idea from a blog post (don't remember the link :-/), but here's the class:
private const val TAG = "SingleLiveData"
/**
* A lifecycle-aware observable that sends only new updates after subscription, used for events like
* navigation and Snackbar messages.
*
* This avoids a common problem with events: on configuration change (like rotation) an update
* can be emitted if the observer is active. This LiveData only calls the observable if there's an
* explicit call to setValue() or call().
*
* Note that only one observer is going to be notified of changes.
*/
open class SingleLiveData<T> : MutableLiveData<T>() {
private val pending = AtomicBoolean(false)
@MainThread
override fun observe(owner: LifecycleOwner, observer: Observer<T>) {
if (hasActiveObservers()) {
Logger.w(TAG, "Multiple observers registered but only one will be notified of changes.")
}
// Observe the internal MutableLiveData
super.observe(owner, wrapObserver(observer))
}
@MainThread
override fun observeForever(observer: Observer<T>) {
if (hasActiveObservers()) {
Logger.w(TAG, "Multiple observers registered but only one will be notified of changes.")
}
super.observeForever(wrapObserver(observer))
}
private fun wrapObserver(observer: Observer<T>): Observer<T> {
return Observer {
if (pending.compareAndSet(true, false)) {
observer.onChanged(it)
}
}
}
@MainThread
override fun setValue(t: T?) {
pending.set(true)
super.setValue(t)
}
/**
* Used for cases where T is Void, to make calls cleaner.
*/
@MainThread
fun call() {
value = null
}
}
Obviously, one problem with this is that it won't permit multiple observers of the same live data. However, if you require that, hopefully this class will give you some ideas.