Android ViewModel onChanged вызывается, когда данные не изменяются

У меня есть Fragment с динамическим числом пользовательских представлений, состоящим из EditText и Button. Что я делаю, так это то, что каждый раз, когда пользователь вводит цену в EditText и нажимает Button, я делаю запрос API через ViewModel, и мой Fragment наблюдает LiveData в ViewModel.

Пока все хорошо, когда я использую первый пользовательский вид. Проблема возникает во втором (и третьем), потому что метод onChanged(), по-видимому, вызывается, даже если данные не изменились, а второе и третье пользовательские представления прослушивают эти данные, поэтому они изменяются, когда они НЕ являются те, которые запускают изменение данных (они получают изменение данных от первого).

Когда пользователь нажимает на Button, я наблюдаю и получаю цену следующим образом:

val observer = Observer<NetworkViewState> { networkViewState ->
            processResponse(networkViewState, moneySpent, coin, date)
        }
        boardingHistoricalPriceViewModel.coinDayAveragePrice.observe(this, observer)
        boardingHistoricalPriceViewModel.getDayAveragePrice(coin.symbol,
                addedCoinDatePriceView.selectedSpinnerItem, dateInMillis)

и что происходит, так это то, что метод processResponse вызывается, когда второе пользовательское представление инициировало запрос API, но результат, который я получаю, - это тот, который coinDayAveragePrice имеет до получения ответа API (это значение после первого ответа API от первого пользовательский вид прибыл).

Это часть моего 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 — это просто sealed class, предназначенный для обертки ответа на запрос API:

sealed class NetworkViewState {
class Loading : NetworkViewState()
class Success<out T>(val item: T) : NetworkViewState()
class Error(val errorMessage: String?) : NetworkViewState()

}

Я также пытался отказаться от подписки или установить для coinDayAveragePrice значение null, но у меня все еще та же проблема.

Заранее большое спасибо!


comment
Пожалуйста, покажите код модели просмотра.   -  person AutonomousApps    schedule 29.04.2018
comment
Следует отметить, что всякий раз, когда вы подписываетесь на живые данные, вы сразу же получаете последнее установленное для них значение, почти как если бы это был rx Java BehaviorSubject.   -  person AutonomousApps    schedule 29.04.2018
comment
опубликовать LivaData<NetworkViewState> coinDayAveragePrice = ... исходный код   -  person pskink    schedule 30.04.2018
comment
@pskink обновил вопрос и добавил еще немного кода, спасибо!   -  person noloman    schedule 30.04.2018
comment
@AutonomousApps да, теперь я вижу, проблема в том, что я снова подписываюсь и, таким образом, я получаю предыдущий результат прямо при подписке, и что все портит   -  person noloman    schedule 30.04.2018
comment
Честно говоря, я чувствую, что вы немного неправильно используете структуру (без обид, это легко сделать!). Попробуйте точно описать, чего вы пытаетесь достичь, на нормальном языке, а затем, возможно, код для реализации, который получится естественным образом.   -  person AutonomousApps    schedule 30.04.2018
comment
@AutonomousApps не удалено. Я очень ценю помощь! действительно, я мог бы неправильно использовать структуру, но в этом случае, как люди делают динамическое количество запросов через ViewModel? динамически создавать LiveData для каждого запроса и наблюдать за ними? P.S.: SingleLiveEvent класс тоже не работает :(   -  person noloman    schedule 30.04.2018
comment
Интуитивно я чувствую, что ваши взгляды не должны напрямую наблюдать за вашими данными в реальном времени. Поскольку у вас есть произвольное количество представлений, размещенных фрагментом, я думаю, вы должны рассматривать свой фрагмент как контроллер, и он должен в основном быть посредником между вашими представлениями и вашей моделью представления. Возможно, другой способ заявить, что ваша модель представления должна предоставлять функцию onClick. Ваши взгляды называют это. Ваш фрагмент наблюдает за оперативными данными и при необходимости изменяет состояние всех ваших представлений.   -  person AutonomousApps    schedule 30.04.2018


Ответы (1)


Итак, не видя вашего ViewModel, трудно точно сказать, в чем проблема, но я думаю, что это то, что я указал в своем комментарии. В этом случае одним из решений является использование другого типа LiveData. Я получил эту основную идею из сообщения в блоге (не помню ссылку :-/), но вот класс:

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
    }
}

Очевидно, одна из проблем заключается в том, что он не позволяет нескольким наблюдателям одних и тех же данных в реальном времени. Однако, если вам это нужно, надеюсь, этот класс даст вам некоторые идеи.

person AutonomousApps    schedule 29.04.2018