BottomNavigation多Fragment下Snackbar重复显示错误消息求助
问题原因
这是LiveData的粘性特性导致的:LiveData会保留最后一次发送的数据,当新的Observer(比如切换回Fragment1/2时重新注册的Observer)订阅时,会立即收到这条旧的错误数据,从而重复弹出Snackbar。
解决方案
下面提供几种可行的解决方式:
方式1:使用SingleLiveEvent(推荐)
这是官方推荐的用于处理单次事件(比如弹窗、导航)的LiveData变种,只会将数据发送给一次订阅的Observer,避免粘性问题。
先实现SingleLiveEvent类:
class SingleLiveEvent<T> : MutableLiveData<T>() { private val pending = AtomicBoolean(false) @MainThread override fun observe(owner: LifecycleOwner, observer: Observer<in T>) { super.observe(owner) { t -> if (pending.compareAndSet(true, false)) { observer.onChanged(t) } } } @MainThread override fun setValue(value: T?) { pending.set(true) super.setValue(value) } @MainThread fun call() { value = null } }
然后将你原来的错误LiveData替换为SingleLiveEvent<Int>,Observer代码可以保持不变,这样错误只会被消费一次,切换Fragment后不会重复触发。
方式2:用事件包装类标记已处理
定义一个包装类来标记事件是否已被处理:
data class Event<out T>(private val content: T) { var hasBeenHandled = false private set fun getContentIfNotHandled(): T? { return if (hasBeenHandled) { null } else { hasBeenHandled = true content } } }
然后你的错误LiveData改为MutableLiveData<Event<Int>>,发送错误时包装成Event:
// 发送错误时 errorLiveData.value = Event(R.string.user_not_found)
修改Observer代码,只处理未被消费的事件:
private val errorObserver = Observer<Event<Int>> { event -> event.getContentIfNotHandled()?.let { resId -> activity?.let { act -> Snackbar.make(act.findViewById(android.R.id.content), resId, Snackbar.LENGTH_SHORT).show() } } }
方式3:处理后重置LiveData值
在Observer处理完错误后,手动将LiveData的值重置为null,这样后续订阅的Observer就不会收到旧数据:
private val errorObserver = Observer<Int>{ resId -> activity?.let { it1 -> Snackbar.make(it1.findViewById(android.R.id.content), resId, Snackbar.LENGTH_SHORT).show() // 处理后重置值 yourErrorLiveData.value = null } }
注意:这个方式要确保yourErrorLiveData是MutableLiveData,且操作在主线程执行。
内容的提问来源于stack exchange,提问作者Abhijith Warrier
相关产品推荐
相关产品推荐

