我使用LiveData
将错误消息发布到控制器(Activity
/ Fragment
)。
设置了错误消息(MutableLiveData.setValue(...)
)后,回调(MutableLiveData.observe(lifecycleOwner, callback)
)会观察到错误消息的值,该回调在Snackbar
UI组件中显示该消息。
Snackbar
仅在短时间内可见,这很好。隐藏Snackbar
之后,我想保持这种状态。
但是,在用户旋转显示之后,将重新创建控制器(Activity
/ Fragment
)并再次通知观察者-因此,Snackbar
再次出现,这不是我想要的
问题:
我以某种方式通过if(!isActivityRecreated()) { showSnackbar(...); }
修复了该行为,但我想知道是否有一种方法可以告诉MutableLiveData
实例仅应在更改的数据上调用观察者,从而忽略控制器的生命周期更改吗? >
(编辑)
继续讨论: https://proandroiddev.com/livedata-with-single-events-2395dea972a8
“ LiveEvent”库:这是作者在第一个链接https://github.com/hadilq/LiveEvent中建议的SingleLiveEvent
的全面实现。与其他解决方案相比,此实现似乎非常健壮。支持多个订阅并且是线程安全的。
答案 0 :(得分:0)
正如@Michal Vician在评论中提到的,我只是将答案弄清楚了。您应该像下面那样扩展MutableLiveData。
public class SingleLiveEvent<T> extends MutableLiveData<T> {
private static final String TAG = "SingleLiveEvent";
private final AtomicBoolean mPending = new AtomicBoolean(false);
@MainThread
public void observe(LifecycleOwner owner, final Observer<T> observer) {
if (hasActiveObservers()) {
Log.w(TAG, "Multiple observers registered but only one will be notified of changes.");
}
// Observe the internal MutableLiveData
super.observe(owner, new Observer<T>() {
@Override
public void onChanged(@Nullable T t) {
if (mPending.compareAndSet(true, false)) {
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);
}
}
答案 1 :(得分:0)
您可以使用this文章中所述的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;
}
}
它重写了observe()方法,从而绕过本地LiveData事件调度机制将观察者保存到内部地图中。 当您重新观察LiveData时,它将不会再次触发onChange。