Android LiveData предотвращает получение последнего значения при подписке
Можно ли помешать LiveData получить последнее значение при начале наблюдения? Я рассматриваю возможность использования LiveData в качестве событий, а не для продвижения значений.
Например, такие события, как показ сообщения, событие навигации или триггер диалога, похожий на EventBus.
Я нашел нечто подобное SingleLiveEvent - но оно работает только для одного наблюдателя, а не для нескольких наблюдателей.
18 ответов
Я не нашел простого решения, поэтому я продолжал style.json
в активах и после монтирования obb скопировал style.json
файл на внешнее хранилище и изменил tiles
URL при копировании.
private void copyStyleWithNewTilesPath() throws IOException {
InputStream myinput = getContext()
.getAssets().open("styleParis.json");
String outfilename = "/data/data/" + Config.APPLICATION_ID + "/map/styleParis.json";
File dbdir = new File(DB_PATH);
if(!dbdir.exists()){
dbdir.mkdirs();
}
OutputStream myoutput = new FileOutputStream(outfilename);
byte[] buffer = new byte[1024];
int length;
while ((length = myinput.read(buffer)) > 0) {
String str = new String(buffer, "UTF-8");
if (str.contains("file://mnt/obb/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx/{z}/{x}/{y}.pbf")) {
buffer = str.replace("file://mnt/obb/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx/{z}/{x}/{y}.pbf", "file://" + MainActivity.obbPath + "/{z}/{x}/{y}.pbf").getBytes("UTF-8");
}
myoutput.write(buffer, 0, length);
}
//Close the streams
myoutput.flush();
myoutput.close();
myinput.close();
}
Я использую этот класс EventWraper из Google Samples внутри MutableLiveData
/**
* Used as a wrapper for data that is exposed via a LiveData that represents an event.
*/
public class Event<T> {
private T mContent;
private boolean hasBeenHandled = false;
public Event( T content) {
if (content == null) {
throw new IllegalArgumentException("null values in Event are not allowed.");
}
mContent = content;
}
@Nullable
public T getContentIfNotHandled() {
if (hasBeenHandled) {
return null;
} else {
hasBeenHandled = true;
return mContent;
}
}
public boolean hasBeenHandled() {
return hasBeenHandled;
}
}
В ViewModel:
/** expose Save LiveData Event */
public void newSaveEvent() {
saveEvent.setValue(new Event<>(true));
}
private final MutableLiveData<Event<Boolean>> saveEvent = new MutableLiveData<>();
LiveData<Event<Boolean>> onSaveEvent() {
return saveEvent;
}
В Деятельности / Фрагмент
mViewModel
.onSaveEvent()
.observe(
getViewLifecycleOwner(),
booleanEvent -> {
if (booleanEvent != null)
final Boolean shouldSave = booleanEvent.getContentIfNotHandled();
if (shouldSave != null && shouldSave) saveData();
}
});
Имея некоторый опыт работы с RxJava, я привык думать, что такие поведенческие требования обычно являются проблемой Observeable
(LiveData
в нашем случае). Существует много операторов, таких как replay (), которые могут управлять тем, что фактически испускается (и когда) по сравнению с фактическими публикациями, сделанными пользователем. По сути, SingleLiveEvent
тоже имеет к нему такое же представление.
Поэтому я придумал эту модифицированную реализацию MutableLiveData
называется VolatileLiveData
:
open class VolatileLiveData<T> : MutableLiveData<T>() {
private val lastValueSeq = AtomicInteger(0)
private val wrappers = HashMap<Observer<in T>, Observer<T>>()
@MainThread
public override fun setValue(value: T) {
lastValueSeq.incrementAndGet()
super.setValue(value)
}
@MainThread
public override fun observe(owner: LifecycleOwner, observer: Observer<in T>) {
val observerWrapper = ObserverWrapper(lastValueSeq, observer)
wrappers[observer] = observerWrapper
super.observe(owner, observerWrapper)
}
@MainThread
public override fun observeForever(observer: Observer<in T>) {
val observerWrapper = ObserverWrapper(lastValueSeq, observer)
wrappers[observer] = observerWrapper
super.observeForever(observerWrapper)
}
@MainThread
public override fun removeObserver(observer: Observer<in T>) {
val observerWrapper = wrappers[observer]
observerWrapper?.let {
wrappers.remove(observerWrapper)
super.removeObserver(observerWrapper)
}
}
}
private class ObserverWrapper<T>(private var currentSeq: AtomicInteger, private val observer: Observer<in T>) : Observer<T> {
private val initialSeq = currentSeq.get()
private var _observer: Observer<in T> = Observer {
if (currentSeq.get() != initialSeq) {
// Optimization: this wrapper implementation is only needed in the beginning.
// Once a valid call is made (i.e. with a different concurrent sequence), we
// get rid of it any apply the real implementation as a direct callthrough.
_observer = observer
_observer.onChanged(it)
}
}
override fun onChanged(value: T) {
_observer.onChanged(value)
}
}
Во-первых, подобно @emandt, я связываю уникальные последовательности с каждым живым значением - но строго в рамках самих живых данных. Эта последовательность устанавливается всякий раз, когда значение устанавливается для текущих данных.
Во-вторых, вдохновленный SingleLiveData
Я ввел обертки вокруг наблюдателя пользователя, которые обращаются к нему только в том случае, если последовательность отличается (т. Е. После подписки было установлено новое значение).
Это в основном подводит итог, но для полной документации, пожалуйста, обратитесь к моей сути.
использование
Что касается его использования - если у вас есть полный контроль над LiveData
, просто используйте VolatileLiveData
как бы вы использовали MutableLiveData
, Если данные изначально приходят откуда-то еще (например, из комнаты), Transformations.switchMap()
может использоваться для "переключения" на энергозависимую реализацию.
Столкнулся с той же проблемой, и я создал несколько простых функций расширения kotlin, которые могут легко решить эту проблему.
Использование как ниже:
val liveData = MutableLiveData<String>()
liveData.value = "Hello"
val freshResult = mutableListOf<String>()
val normalResult = mutableListOf<String>()
liveData.observeForeverFreshly(Observer {
freshResult.add(it)
})
liveData.observeForever(Observer {
normalResult.add(it)
})
liveData.value = "World"
assertEquals(listOf("World"), freshResult)
assertEquals(listOf("Hello", "World"), normalResult)
Базовый исходный код объясняется как блуд.
Для более подробной информации (например, для поддержки некоторых особых ситуаций) MediatorLiveData
возвращено из Transformations.map), вы можете просмотреть его в github: aliveata-ext
FreshLiveData.kt
fun <T> LiveData<T>.observeFreshly(owner: LifecycleOwner, observer: Observer<in T>) {
// extention fuction to get LiveData's version, will explain in below.
val sinceVersion = this.version()
this.observe(owner, FreshObserver<T>(observer, this, sinceVersion))
}
fun <T> LiveData<T>.observeForeverFreshly(observer: Observer<in T>, skipPendingValue: Boolean = true) {
val sinceVersion = this.version()
this.observeForever(FreshObserver<T>(observer, this, sinceVersion))
}
// Removes the observer which has been previously observed by [observeFreshly] or [observeForeverFreshly].
fun <T> LiveData<T>.removeObserverFreshly(observer: Observer<in T>) {
this.removeObserver(FreshObserver<T>(observer, this, 0))
}
class FreshObserver<T>(
private val delegate: Observer<in T>,
private val liveData: LiveData<*>,
private val sinceVersion: Int
) : Observer<T> {
override fun onChanged(t: T) {
if (liveData.version() > sinceVersion) {
delegate.onChanged(t)
}
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as FreshObserver<*>
if (delegate != other.delegate) return false
return true
}
override fun hashCode(): Int {
return delegate.hashCode()
}
}
Потому что нам нужно получить доступ к pcakage видимости метадона LiveData getVersion()
для сравнения, поэтому создайте класс в пакете android.arch.lifecycle
или же androidx.lifecycle
(AndroidX):
LiveDataHiddenApi.kt
package androidx.lifecycle
fun LiveData<*>.version(): Int {
return this.getVersion()
}
Я не думаю, что возможно запретить LiveData получать последнее значение при начале наблюдения, если вы используете их как есть. Что вы можете сделать, это продлить ViewModel
Класс и сделать его уведомить мнение, только если добавлен наблюдатель.
Другой вариант - просто игнорировать обратный вызов.
Добавьте флаг в ViewModel.
private boolean isFirstTime = true; public boolean getIsFirstTime() { return isFirstTime; } public boolean onObserverAdded() { isFirstTime = false; }`
Добавить проверку в обратном вызове
@Override public void onChanged(@Nullable final String newName) { boolean ignore = ((MyViewModel)ViewModelProviders.of(MyActivity.this).get(MyViewModel.class)).getIsFirstTime(); if(ignore) return; // Update the UI }
Наконец позвони
onObserverAdded()
после добавления наблюдателя.
Я создал новый класс, который будет содержать мои реальные данные и "специальный идентификатор":
class LiveDataItem {
long mRealtimeNanos;
YOUR_PREVIOUS_LIVEDATA_TYPE mData;
LiveDataItem(YOUR_PREVIOUS_LIVEDATA_TYPE data, long realtimeNanos) {
this.mRealtimeNanos = realtimeNanos;
this.mData = data;
}
}
Затем я создал новую "глобальную" переменную:
final List<Long> mExcludedRealtimeNanos = new ArrayList<>;
На этом этапе я выбираю "set/postValue()" моего типа "LiveDataItem" вместо исходного типа "YOUR_PREVIOUS_LIVEDATA_TYPE" с помощью нового и специального метода "postValue()":
public void myPostValue(YOUR_PREVIOUS_LIVEDATA_TYPE data, boolean notifyWhenObserved) {
long cRealtimeNanos = SystemClock.realtimeNanos();
if (!notifyWhenObserved) mExcludedRealtimeNanos.add(cRealtimeNanos);
....postValue(new LiveDataItem(data, cRealtimeNanos));
}
Затем я создал обычный Observer, который будет получать все события "Changed()", и внутри него я поставил проверку "RealtimeNanos":
public void onChanged(LiveDataItem myDataItem) {
boolean cFound = false;
for (Long cRealtimeNanos : mExcludedRealtimeNanos) {
if (cRealtimeNanos == myDataItem.mRealtimeNanos) {
cFound = true;
break;
}
}
//check if it was found --> NO: it means that I wish to get the notification
if (!cFound) mMyOnChangedCallback(myDataItem.mData)
}
Очевидно, что метод "mMyOnChangedCallback()" является функцией обратного вызова, которая будет вызываться всякий раз, когда вызывается исходное событие "onChanged()", НО только в том случае, если вы настроите его на уведомление во время создания данных.
Вы можете выбрать получение уведомлений снова, просто удалив THAT RealtimeNanos из "mExcludedRealtimeNanos", а затем присоедините новый наблюдатель к этим LiveData.
Несколько изменений могут улучшить этот код, но я написал вам то, что помню о моем старом коде (в данный момент я в данный момент не на своем компьютере). Например, мы можем решить удалить значение из "mExcludedRealtimeNanos", когда новые данные публикуются с помощью нашего пользовательского метода postValue ()....
Согласно ответу Jurij Pitulja.
если мы используем
kotlin coroutines
решение выглядит так.
class Event<T>(private val content: T) {
var isHandled = false
private set
fun getContentIfNotHandled(): T? {
return takeIf { !isHandled }?.let {
isHandled = true
content
}
}
}
Внутри
view model
класс заменяющий
Flow.asLiveData()
в
emit new Event
val authResult: LiveData<Event<Result<AuthResponse>>> = _emailLiveData.switchMap { email ->
liveData{
repository.authRequest(email).collect{
emit(Event(it))
}
}
}
Реализация
observer
метод внутри
fragment
viewModel.authResult.observe(viewLifecycleOwner){
it.getContentIfNotHandled()?.run {
onAuthRequestComplete(this)
}
}
Предполагая, что вы наблюдаете данные в реальном времени из действия, фрагмента или другого представления с жизненным циклом, простым подходом было бы игнорировать начальное «последнее значение», ничего не делая, если наблюдатель уведомлен, когда действие или фрагмент не видны/ возобновлено еще:
MainActivity.kt
myviewmodel.myLiveData.observe(this) {
if (lifecycle.currentState != Lifecycle.State.RESUMED) {
return@observe
}
}
или, если вы наблюдаете за виртуальной машиной из фрагмента:
myviewmodel.myLiveData.observe(viewLifecycleOwner) {
if (viewLifecycleOwner.lifecycle.currentState != Lifecycle.State.RESUMED) {
return@observe
}
}
Любой компонент с жизненным циклом, отслеживающий оперативные данные, может выполнять эту проверку каждый раз, когда получает обновление. Если компонент в данный момент не виден на экране, мы отбрасываем обновление и ничего не делаем. Как только мы наблюдаем, компонент по-прежнему будет получать данные своего последнего значения от ВМ, но еще не будет виден/возобновлен, поэтому мы просто его игнорируем.
Я создал объект LiveData FreshLiveData
, который излучает onChange
к наблюдателю только после вызова setValue
или postValue
.
FreshLiveData.kt
/**
* A lifecycle-aware observable that emits only new data after subscription. Any data that has
* already been set, before the observable has subscribed, will be ignored.
*
* This avoids a common problem with events: on configuration change (like rotation, font change) 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 postValue().
*
* All observers will be notified of change(s).
*/
class FreshLiveData<T> : MutableLiveData<T>() {
private val observers = mutableMapOf<LifecycleOwner, FreshLiveDataObserver>()
override fun observe(owner: LifecycleOwner, observer: Observer<in T>) {
@Suppress("UNCHECKED_CAST")
observer as Observer<T>
observers[owner].apply {
if (this == null) {
observers[owner] = FreshLiveDataObserver(observer).apply {
super.observe(owner, this)
}
} else {
add(observer)
}
}
}
override fun observeForever(observer: Observer<in T>) {
@Suppress("UNCHECKED_CAST")
observer as Observer<T>
observers[ProcessLifecycleOwner.get()].apply {
if (this == null) {
observers[ProcessLifecycleOwner.get()] = FreshLiveDataObserver(observer).apply {
super.observeForever(this)
}
} else {
add(observer)
}
}
}
override fun removeObservers(owner: LifecycleOwner) {
observers.remove(owner)
super.removeObservers(owner)
}
override fun removeObserver(observer: Observer<in T>) {
@Suppress("UNCHECKED_CAST")
observers.forEach { it.value.remove(observer as Observer<T>) }
super.removeObserver(observer)
}
@MainThread
override fun setValue(t: T?) {
observers.forEach { it.value.setPending() }
super.setValue(t)
}
override fun postValue(value: T) {
observers.forEach { it.value.setPending() }
super.postValue(value)
}
inner class FreshLiveDataObserver(observer: Observer<T>) : Observer<T> {
private val observers = mutableSetOf<Observer<T>>()
private val pending = AtomicBoolean(false)
init {
observers.add(observer)
}
fun add(observer: Observer<T>) = observers.add(observer)
fun remove(observer: Observer<T>) = observers.remove(observer)
fun setPending() = pending.set(true)
override fun onChanged(t: T) {
if (pending.compareAndSet(true, false)) {
observers.forEach { observer ->
observer.onChanged(t)
}
}
}
}
}
а вот расширение для преобразования существующего LiveData
к FreshLiveData
.
LiveDataExtensions.kt
@MainThread
fun <T> LiveData<T>.toFreshLiveData(): LiveData<T> {
val freshLiveData = FreshLiveData<T>()
val output = MediatorLiveData<T>()
// push any onChange from the LiveData to the FreshLiveData
output.addSource(this) { liveDataValue -> freshLiveData.value = liveDataValue }
// then push any onChange from the FreshLiveData out
output.addSource(freshLiveData) { freshLiveDataValue -> output.value = freshLiveDataValue }
return output
}
Применение:
val liveData = MutableLiveData<Boolean>()
liveData.value = false
liveData.toFreshLiveData().observeForever {
// won't get called with `it = false` because the observe was setup after setting that livedata value
// will get called with `it = true` because the observer was setup before setting that livedata value
}
liveData.value = false
val freshLiveData = FreshLiveData<Boolean>()
freshLiveData.value = false
freshLiveData.observeForever {
// won't get called with `it = false` because the observe was setup after setting that livedata value
// will get called with `it = true` because the observer was setup before setting that livedata value
}
freshLiveData.value = true
Даже у меня было такое же требование. Я добился этого путем расширения MutableLiveData
package com.idroidz.android.ion.util;
import android.arch.lifecycle.LifecycleOwner;
import android.arch.lifecycle.MutableLiveData;
import android.arch.lifecycle.Observer;
import android.support.annotation.MainThread;
import android.support.annotation.Nullable;
import java.util.concurrent.atomic.AtomicBoolean;
public class VolatileMutableLiveData<T> extends MutableLiveData<T> {
private final AtomicBoolean mPending = new AtomicBoolean(false);
@MainThread
public void observe(LifecycleOwner owner, final Observer<T> observer) {
// Observe the internal MutableLiveData
mPending.set(false);
super.observe(owner, new Observer<T>() {
@Override
public void onChanged(@Nullable T t) {
if (mPending.get()) {
observer.onChanged(t);
}
}
});
}
@MainThread
public void setValue(@Nullable T t) {
mPending.set(true);
super.setValue(t);
}
/**
* Used for cases where T is Void, to make calls cleaner.
*/
@MainThread
public void call() {
setValue(null);
}
public void callFromThread() {
super.postValue(null);
}
}
Нет причин использовать LiveData как нечто, чем она не является. Если вам нужно отдельное поведение (что-то, что не сохраняет предыдущее значение), вы должны использовать компонент, который не сохраняет предыдущее значение, вместо того, чтобы взламывать его ("вспоминая", что он испустил, а затем забывая излучать и т. д.)
Можете добавить event-emitter
библиотека:
implementation 'com.github.Zhuinden:event-emitter:1.1.0'
из Jitpack: maven { url "https://jitpack.io" }
Тогда ты можешь сделать
// read
private var subscription: EventSource.NotificationToken? = null
fun observe() {
subscription = events.startListening { event ->
showToast(event)
}
}
fun unsubscribe() {
subscription?.stopListening()
subscription = null
}
Но если вам нужно использовать это так:
controllerEvents.observe(viewLifecycleOwner) { event: WordController.Events ->
when (event) {
is WordController.Events.NewWordAdded -> showToast("Added ${event.word}")
}.safe()
}
Тогда вы можете использовать
import android.arch.lifecycle.Lifecycle import android.arch.lifecycle.LifecycleObserver import android.arch.lifecycle.LifecycleOwner import android.arch.lifecycle.OnLifecycleEvent import com.zhuinden.eventemitter.EventSource private class LiveEvent<T> constructor( private val eventSource: EventSource<T>, private val lifecycleOwner: LifecycleOwner, private val observer: EventSource.EventObserver<T> ) : LifecycleObserver { init { if (lifecycleOwner.lifecycle.currentState.isAtLeast(Lifecycle.State.INITIALIZED)) { lifecycleOwner.lifecycle.addObserver(this) } } private var isActive: Boolean = false private var notificationToken: EventSource.NotificationToken? = null private fun shouldBeActive(): Boolean { return lifecycleOwner.lifecycle.currentState.isAtLeast(Lifecycle.State.STARTED) } private fun disposeObserver() { lifecycleOwner.lifecycle.removeObserver(this) } @OnLifecycleEvent(Lifecycle.Event.ON_ANY) fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) { if (lifecycleOwner.lifecycle.currentState == Lifecycle.State.DESTROYED) { stopListening() disposeObserver() return } checkIfActiveStateChanged(shouldBeActive()) } private fun checkIfActiveStateChanged(newActive: Boolean) { if (newActive == isActive) { return } val wasActive = isActive isActive = newActive val isActive = isActive if (!wasActive && isActive) { stopListening() notificationToken = eventSource.startListening(observer) } if (wasActive && !isActive) { stopListening() } } private fun stopListening() { notificationToken?.stopListening() notificationToken = null } } fun <T> EventSource<T>.observe(lifecycleOwner: LifecycleOwner, eventObserver: (T) -> Unit) { LiveEvent(this, lifecycleOwner, EventSource.EventObserver<T> { event -> eventObserver.invoke(event) }) }
Я написал небольшой служебный класс, который позволяет мне быстро блокировать первый запуск любого блока кода, например:
class SkipFirstRun {
var canRun = false
fun run(codeToRunButNotFirstTime: () -> Unit) {
if (canRun) codeToRunButNotFirstTime()
canRun = true
}
}
В моем фрагменте я просто объявляю его экземпляр var:
class TrainingDetailCardInfoFragment
override val viewModel: TrainingViewModel by viewModels
private var skipInitialEndOfTraining = SkipFirstRun()
...
а затем использовать его так
viewModel.runningTask.observe(viewLifecycleOwner) { task ->
skipInitialEndOfTraining.run {
Log.debug("Playing stopped!")
sound.play(requireContext(), R.raw.endoftraining)
playBarViewModel.stop()
}
}
Более простым решением было бы использовать EventLiveData lib:
implementation 'com.rugovit.eventlivedata:eventlivedata:1.0'
MutableEventLiveData<String> eventLiveData =new MutableEventLiveData<>();
viewModel.event.observe(this, Observer {
// ...
})
Вы используете его как обычные данные в реальном времени. Это расширение liveata и поддерживает все функции liveata. В отличие от других решений, это поддерживает несколько наблюдателей.
Ссылка на Github: https://github.com/rugovit/EventLiveData
Вы можете использовать EventLiveData, описанный в этой статье. Это расширение LiveData, как и SingleLiveData, но поддерживает несколько наблюдателей. Также позволяет настраивать ограничение жизненного цикла, когда наблюдатели должны получать события. Например, если вы не хотите получать события, когда ваши фрагменты находятся в фоновом режиме.
EventLiveData содержит внутреннего наблюдателя, за которым следит вечно, переопределяет метод наблюдения, сохраняющий наблюдателей во внутреннюю карту, минуя собственный механизм отправки событий LiveData.
public class EventLiveData<T> extends LiveData<T> {
private final HashMap<Observer<? super T>, EventObserverWrapper> observers= new HashMap<>();
private final Observer<T> internalObserver;
int mActiveCount = 0;
public EventLiveData() {
this.internalObserver = (new Observer<T>() {
@Override
public void onChanged(T t) {
Iterator<Map.Entry<Observer<? super T>, EventObserverWrapper>> iterator = EventLiveData.this.observers.entrySet().iterator();
while (iterator.hasNext()){
EventObserverWrapper wrapper= iterator.next().getValue();
if(wrapper.shouldBeActive())
wrapper.getObserver().onChanged(t);
}
}
});
}
private void internalObserve(){
super.observeForever(this.internalObserver);
}
@MainThread
@Override
public void observe(@NonNull LifecycleOwner owner, @NonNull Observer observer) {
observe(owner, observer,STARTED,null);
}
@MainThread
public void observe(@NonNull LifecycleOwner owner, @NonNull Observer observer, @NonNull Lifecycle.State minimumStateForSendingEvent) {
observe(owner, observer,minimumStateForSendingEvent,null);
}
@MainThread
public void observeInOnStart(@NonNull LifecycleOwner owner, @NonNull Observer observer) {
observe(owner, observer,STARTED, Lifecycle.Event.ON_STOP);
}
@MainThread
public void observe(@NonNull LifecycleOwner owner, @NonNull Observer observer, @NonNull Lifecycle.State minimumStateForSendingEvent, Lifecycle.Event removeObserverEvent) {
assertMainThread("observe");
assertNotNull(owner, "owner");
assertNotNull(observer, "observer");
assertNotNull(owner, "minimumStateForSendingEvent");
assertDestroyedState(minimumStateForSendingEvent);
assertMaximumEvent(removeObserverEvent);
if(minimumStateForSendingEvent==DESTROYED){
StackTraceElement[] stackTraceElements = Thread.currentThread().getStackTrace();
StackTraceElement caller = stackTraceElements[3];
String className = caller.getClassName();
String methodName = caller.getMethodName();
IllegalArgumentException exception =
new IllegalArgumentException("State can not be equal to DESTROYED! : " +
"method " + className + "." + methodName +
", parameter " + minimumStateForSendingEvent);
throw sanitizeStackTrace(exception);
}
if (owner.getLifecycle().getCurrentState() == DESTROYED) {
return;
}
EventLifecycleBoundEventObserver wrapper = new EventLifecycleBoundEventObserver(owner, observer);
wrapper.setMinimumStateForSendingEvent(minimumStateForSendingEvent);
wrapper.setMaximumEventForRemovingEvent(removeObserverEvent);
EventObserverWrapper existing = wrapper;
if(!observers.containsKey(observer))existing = observers.put(observer, wrapper);
if (existing != null && !existing.isAttachedTo(owner)) {
throw new IllegalArgumentException("Cannot add the same observer"
+ " with different lifecycles");
}
if (existing != null) {
return;
}
owner.getLifecycle().addObserver(wrapper);
if (!super.hasObservers()) {
internalObserve();
}
}
@MainThread
@Override
public void observeForever(@NonNull Observer observer) {
assertMainThread("observeForever");
assertNotNull(observer, "observer");
EventAlwaysActiveEventObserver wrapper = new EventAlwaysActiveEventObserver(observer);
EventObserverWrapper existing = wrapper;
if(!observers.containsKey(observer))existing = observers.put(observer, wrapper);
if (existing != null && existing instanceof EventLiveData.EventLifecycleBoundEventObserver) {
throw new IllegalArgumentException("Cannot add the same observer"
+ " with different lifecycles");
}
if (existing != null) {
return;
}
if (!super.hasObservers()) {
internalObserve();
}
wrapper.activeStateChanged(true);
}
/**
{@inheritDoc}
*/
@Override
public void removeObservers(@NonNull LifecycleOwner owner) {
assertMainThread("removeObservers");
assertNotNull(owner, "owner");
Iterator<Map.Entry<Observer<? super T>, EventObserverWrapper>> iterator = EventLiveData.this.observers.entrySet().iterator();
while (iterator.hasNext()){
Map.Entry<Observer<? super T>, EventObserverWrapper> entry=iterator.next();
if(entry.getValue() instanceof EventLiveData.EventLifecycleBoundEventObserver){
EventLifecycleBoundEventObserver eventLifecycleBoundObserver =(EventLifecycleBoundEventObserver) entry.getValue();
if(eventLifecycleBoundObserver.isAttachedTo(owner))this.observers.remove(entry.getKey());
}
}
}
@Override
public void removeObserver(@NonNull Observer observer) {
assertMainThread("removeObserver");
assertNotNull(observer, "observer");
this.observers.remove(observer);
}
final protected void onActive() {}
protected void onActiveEvent() {}
protected void onInactive() {
}
@SuppressWarnings("WeakerAccess")
public boolean hasObservers() {
return observers.size() > 0;
}
@SuppressWarnings("WeakerAccess")
public boolean hasActiveObservers() {
return mActiveCount > 0;
}
class EventLifecycleBoundEventObserver extends EventObserverWrapper implements LifecycleObserver {
@NonNull
private final LifecycleOwner mOwner;
private Lifecycle.State MINIMUM_STATE_FOR_SENDING_EVENT= STARTED;
private Lifecycle.Event MAXIMUM_EVENT_FOR_REMOVING_EVENT= null;
EventLifecycleBoundEventObserver(@NonNull LifecycleOwner owner, Observer<? super T> observer) {
super(observer);
mOwner = owner;
}
public Lifecycle.State getMinimumStateForSendingEvent() {
return MINIMUM_STATE_FOR_SENDING_EVENT;
}
public Lifecycle.Event getMaximumStateForRemovingEvent() {
return MAXIMUM_EVENT_FOR_REMOVING_EVENT;
}
public void setMaximumEventForRemovingEvent(Lifecycle.Event MAXIMUM_EVENT_FOR_REMOVING_EVENT) {
this.MAXIMUM_EVENT_FOR_REMOVING_EVENT = MAXIMUM_EVENT_FOR_REMOVING_EVENT;
}
public void setMinimumStateForSendingEvent(Lifecycle.State MINIMUM_STATE_FOR_SENDING_EVENT) {
this.MINIMUM_STATE_FOR_SENDING_EVENT = MINIMUM_STATE_FOR_SENDING_EVENT;
}
@Override
boolean shouldBeActive() {
Lifecycle.State state=mOwner.getLifecycle().getCurrentState();
return state.isAtLeast(MINIMUM_STATE_FOR_SENDING_EVENT);
}
@OnLifecycleEvent(Lifecycle.Event.ON_ANY)
public void onStateChanged(LifecycleOwner source, Lifecycle.Event event) {
if (mOwner.getLifecycle().getCurrentState() == DESTROYED||(MAXIMUM_EVENT_FOR_REMOVING_EVENT!=null&&MAXIMUM_EVENT_FOR_REMOVING_EVENT==event)) {
removeObserver(mObserver);
return;
}
activeStateChanged(shouldBeActive());
}
@Override
boolean isAttachedTo(LifecycleOwner owner) {
return mOwner == owner;
}
@Override
void detachObserver() {
mOwner.getLifecycle().removeObserver(this);
}
}
private abstract class EventObserverWrapper {
protected final Observer<? super T> mObserver;
boolean mActive;
EventObserverWrapper(Observer<? super T> observer) {
mObserver = observer;
}
abstract boolean shouldBeActive();
boolean isAttachedTo(LifecycleOwner owner) {
return false;
}
void detachObserver() {
}
public Observer<? super T> getObserver() {
return mObserver;
}
void activeStateChanged(boolean newActive) {
if (newActive == mActive) {
return;
}
// immediately set active state, so we'd never dispatch anything to inactive
// owner
mActive = newActive;
boolean wasInactive = EventLiveData.this.mActiveCount == 0;
EventLiveData.this.mActiveCount += mActive ? 1 : -1;
if (wasInactive && mActive) {
onActiveEvent();
}
if (EventLiveData.this.mActiveCount == 0 && !mActive) {
onInactive();
}
}
}
private class EventAlwaysActiveEventObserver extends EventObserverWrapper {
EventAlwaysActiveEventObserver(Observer<? super T> observer) {
super(observer);
}
@Override
boolean shouldBeActive() {
return true;
}
}
private void assertDestroyedState(@NonNull Lifecycle.State minimumStateForSendingEvent){
if(minimumStateForSendingEvent==DESTROYED){
StackTraceElement[] stackTraceElements = Thread.currentThread().getStackTrace();
StackTraceElement caller = stackTraceElements[3];
String className = caller.getClassName();
String methodName = caller.getMethodName();
IllegalArgumentException exception =new IllegalArgumentException("State can not be equal to "+ minimumStateForSendingEvent +"method " + className + "." + methodName +", parameter minimumStateForSendingEvent");
throw sanitizeStackTrace(exception);}
}
private void assertMaximumEvent(@NonNull Lifecycle.Event maximumEventForRemovingEvent){
if(maximumEventForRemovingEvent== Lifecycle.Event.ON_START||maximumEventForRemovingEvent== Lifecycle.Event.ON_CREATE
||maximumEventForRemovingEvent== Lifecycle.Event.ON_RESUME){
StackTraceElement[] stackTraceElements = Thread.currentThread().getStackTrace();
StackTraceElement caller = stackTraceElements[3];
String className = caller.getClassName();
String methodName = caller.getMethodName();
IllegalArgumentException exception = new IllegalArgumentException("State can not be equal to "+maximumEventForRemovingEvent + "method " + className + "." + methodName +", parameter maximumEventForRemovingEvent" );
throw sanitizeStackTrace(exception);
}
}
private void assertMainThread(String methodName) {
boolean isUiThread = Build.VERSION.SDK_INT >= Build.VERSION_CODES.M ? Looper.getMainLooper().isCurrentThread() : Thread.currentThread() == Looper.getMainLooper().getThread();
if (!isUiThread) {throw new IllegalStateException("Cannot invoke " + methodName + " on a background"+ " thread"); }
}
private void assertNotNull(Object value, String paramName) {
if (value == null) {throwParameterIsNullException(paramName); } }
private void throwParameterIsNullException(String paramName) {
StackTraceElement[] stackTraceElements = Thread.currentThread().getStackTrace();
StackTraceElement caller = stackTraceElements[3];
String className = caller.getClassName();
String methodName = caller.getMethodName();
IllegalArgumentException exception =
new IllegalArgumentException("Parameter specified as non-null is null: " +
"method " + className + "." + methodName +
", parameter " + paramName);
throw sanitizeStackTrace(exception);
}
private <T extends Throwable> T sanitizeStackTrace(T throwable) { return sanitizeStackTrace(throwable, this.getClass().getName());}
<T extends Throwable> T sanitizeStackTrace(T throwable, String classNameToDrop) {
StackTraceElement[] stackTrace = throwable.getStackTrace();
int size = stackTrace.length;
int lastIntrinsic = -1;
for (int i = 0; i < size; i++) {
if (classNameToDrop.equals(stackTrace[i].getClassName())) {lastIntrinsic = i; } }
StackTraceElement[] newStackTrace = Arrays.copyOfRange(stackTrace, lastIntrinsic + 1, size);
throwable.setStackTrace(newStackTrace);
return throwable;
}
}
Просто игнорируйте данные перед android.arch.lifecycle.LiveData#observe
функция вызывается.
class IgnoreHistoryLiveData<T> : MutableLiveData<T>() {
private val unactivedObservers = LinkedBlockingQueue<WrapperObserver<T>>()
override fun observe(owner: LifecycleOwner, observer: Observer<T>) {
val wo = WrapperObserver<T>(observer)
unactivedObservers.add(wo)
super.observe(owner, wo)
}
override fun setValue(value: T) {
while (unactivedObservers.isNotEmpty()) {
unactivedObservers.poll()?.actived = true
}
super.setValue(value)
}
}
private class WrapperObserver<T>(private val origin: Observer<T>) : Observer<T> {
var actived = false
override fun onChanged(t: T?) {
if (actived) {
origin.onChanged(t)
}
}
}
У меня есть простое решение:
class HotLiveData<T> : MutableLiveData<T>() {
override fun observe(owner: LifecycleOwner, observer: Observer<in T>) {
// If live data has been initialized, the first event is ignored
if (isInitialized) {
super.observe(owner, NonStickyObserver(observer))
} else {
super.observe(owner, observer)
}
}
private class NonStickyObserver<T>(private val observer: Observer<T>) : Observer<T>{
private var firstInvocation = true
override fun onChanged(value: T) {
if (firstInvocation) {
firstInvocation = false
} else {
observer.onChanged(value)
}
}
}
}
The LiveData.isInitialized
метод добавлен в lifecycle-livedata 2.6.0
Я неоднократно сталкивался с этой проблемой, и похоже, что это проблема с Flow и новым рекомендуемым руководством по использованию одиночных событий пользовательского интерфейса . Причина, по которой мне трудно понять, заключается в том, что существуют тонкие различия в том, как вам нужно потреблять события.
Знаменитый EventWrapper обрабатывает вариант использования:
начать наблюдение -> событие происходит -> восстановить наблюдение и убедиться, что мы не получим это событие снова
Однако что, если акт наблюдения в первую очередь также включает в себя последние данные? Кажется, что ни одно из официальных руководств не касается того, что если вы публикуете что-то в LiveData по дизайну, оно будет распространяться при первом наблюдении , если оно еще не наблюдалось , даже если вы используете Flow или EventWrapper.
Есть:
- События, которые вы хотите использовать только один раз (SingleLiveEvent)
- События, которые вы хотите, чтобы несколько наблюдателей потребляли только один раз (EventWrapper или текущее руководство по архитектуре с использованием StateFlow + списки + идентификаторы с загрузкой сумасшедшего шаблона)
- И события, которые вы хотите использовать, только после того, как вы начнете наблюдать за одним или несколькими наблюдателями (этот вопрос)
Это особенно проблема, когда модель представления совместно используется двумя фрагментами. т. е. Фрагмент A отправляет значение в оперативные данные, а затем через несколько кликов или сетевых ответов Фрагмент B появляется на экране и начинает наблюдать те же общие оперативные данные, и вы хотите, чтобы он выбрасывал этот последний результат и использовал только будущие. В этом случае вы используете LiveData для чего-то, для чего он не предназначен , и вам нужно посмотреть на условия, почему фрагмент B не должен заботиться о последнем результате и использовать логику в наблюдении , чтобы отфильтровать его.
Проверьте это: https://github.com/san-sk/android-kotlin-extensions/blob/main/LiveDataUtils.kt
/**
Required Lib
Kotlin reflection - https://mvnrepository.com/artifact/org.jetbrains.kotlin/kotlin-reflect
*/
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.LiveData
import androidx.lifecycle.Observer
/**
* Extension to access the private functions of a class
*/
inline fun <reified T> T.callPrivateFunc(name: String, vararg args: Any?): Any? {
val classArray: Array<Class<*>> = args.map { it!!::class.java }.toTypedArray()
return T::class.java.getDeclaredMethod(name, *classArray)
.apply { isAccessible = true }
.invoke(this, *args)
}
/**
* Get LiveData private function [LiveData.getVersion]
*
* Uses reflection concept
*/
fun LiveData<*>.version(): Int {
return this.callPrivateFunc("getVersion") as Int
}
/**
* Use this to get the latest value from live data.
* This will skip the initial value and emit the latest one.
*
* Usage: to get values only after hitting the api
*/
fun <T> LiveData<T>.observeLatest(owner: LifecycleOwner, observer: Observer<in T>) {
val sinceVersion = this.version()
this.observe(owner, LatestObserver<T>(observer, this, sinceVersion))
}
fun <T> LiveData<T>.observeForeverLatest(
observer: Observer<in T>,
skipPendingValue: Boolean = true
) {
val sinceVersion = this.version()
this.observeForever(LatestObserver<T>(observer, this, sinceVersion))
}
// Removes the observer which has been previously observed by [observeFreshly] or [observeForeverFreshly].
fun <T> LiveData<T>.removeObserverLatest(observer: Observer<in T>) {
this.removeObserver(LatestObserver<T>(observer, this, 0))
}
class LatestObserver<T>(
private val delegate: Observer<in T>,
private val liveData: LiveData<*>,
private val sinceVersion: Int
) : Observer<T> {
override fun onChanged(t: T) {
if (liveData.version() > sinceVersion) {
delegate.onChanged(t)
}
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
if (delegate != (other as LatestObserver<*>).delegate) return false
return true
}
override fun hashCode(): Int {
return delegate.hashCode()
}
}