LiveData
里面设置的数据貌似不能被清空。不能将其看做一个容器,没有对应的remove(element)
的方法。背景:Activity
里面有一个以上的 Fragment
, 其中一个,叫做PassiveFragment
。这个Fragment
一般不会显示出来,只有两种情况下回显示出来:
1. 用户点击了其他Fragment
的某些按钮。
2. 或者收到某些系统事件,比如WIFI
断开这种事件。
只有以上两种情况会弹出来。
然后,在Repository
里面注册了WIFI
断开的监听,监听到了,就在里面设置数据。类似
// SingletonRepository.java
private MutableLiveData<Boolean> mWifiDisconnected = new MutableLiveData<>();
private Receiver mWifiReceiver = (intent, action)-> {
if(wifiDisconnected){
mWifiDisconnected.setValue(true);
}
}
public void init() {
registerWifiReceiver();
// other codes...
}
public void release() {
unregisterWifiReceiver();
// other codes...
}
// other codes...
然后在 PassiveFragmentViewModel
里面会去获取对应的LiveData
.
// PassiveFragmentViewModel.java
public LiveData<Boolean> isWifiDisconnected() {
return SingletonRepository.getInstance().isWifiDisconnected();
}
在 PassiveFragment
中会根据viewmodel
加载的数据去显示不同的内容,比如收到 WIFI 断开的时候,就显示一个 WIFI 断开的图标;如果是用户正常点击进来的,就显示其他的东西。
那么,很自然的,在PassiveFragment
大概是这样的逻辑:
// PassiveFragment.java
private PassiveFragmentViewModel mViewModel;
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
// ...
mViewModel.isWifiDisconnected().observer(this, (Boolean)disconnected -> {
// 这里的 boolean 类型是不用的,写出来是为了方便阅读。
Log.i(TAG, "receive wifi disconnected message.");
if(disconnected) {
showDisconnectedView();
} else {
showOtherView();
}
}
}
ok, 大致的代码就是上面这样的。
粗略一看,也没有什么逻辑问题。实际运行发现页面显示也都还正常。
但是:如果第一次是收到 WIFI 断开进来的,然后用户点击返回键,退出PassiveFragment
页面,回到上一个 Fragment
。然后这时,用户点击对应的按钮,主动跳到 PassiveFragment
里面,会发现上面的observer()
又执行了,因为 Log.i(TAG, "receive wifi disconnected message.");
确实在这时候也打印出来了。
明显,上述现象不符合预期。预期是用户点击进来的,就不要走 observer
方法,只有是收到系统回调设置数据了才走进去。
那么,这个问题是怎么出现的呢,又该怎么改呢?
=====
通过看LiveData/ViewModel
文档会发现,这就是它的设计。你只要设置了数据,然后它自动感应你的生命周期,只要是激活状态,它就会给你回调了。
那么,我首先的想法就是,这时候能不能把之前设置的数据清空掉?下次进来让这个LiveData
不要给我数据回调了。反正,我不需要上次的数据了。
不过看LiveData
里面没有提供这样的接口。那么,我在显示之后,粗暴地给对应的LiveData
调用LiveData#setValue(null);
行不行呢?
不行。因为null
也被认为是一个数据,到时候还是给你回调,只是这次回调给你的不是 true/false
, 而是一个null
。明显,这样也不符合预期,而且更不好了。
后面,我想了两种可行的解决方案:
LiveData
去保存,用普通变量boolean
去保存即可。然后通过fragment.setArgments()
这个方法去传递。如果是用户点击就传递 false
或者不传;如果是收到WIFI断开的监听就传递一个true
过去。这样每次 PassiveFragment.newInstance(disconnected);
就能保证数据是正常的。LiveData
,拿到数据后显示,然后就粗暴地调用LiveData#setValue(null);
只是,每次拿数据的时候先做一个非空判断。当然,感觉还是第一种更好。更直观。
==========
然后,如果是使用第一种方案,就需要解决另外一个问题,就是如何在你的WifiReceiver
里面获取FragmentManager/Activity
这些,不然,没办法进行 Fragment
切换的操作了。
如何解决这个衍生的问题?
说一下我想到的解决方案:
Activity
的内部类,这样就解决这个问题了;Activity
里面;不推荐使用 EventBus/RxBus/LiveDataBus
这种库。感觉这种库在调用的时候是很方便的。只是很容易滥用。导致看代码就有点麻烦了。
======
针对第二种方案的简单封装处理,让 null 不被回调出来。
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.lifecycle.LifecycleOwner;
import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.Observer;
import java.util.Objects;
public class NonNullLiveData<T> extends MutableLiveData<T> {
@Override
protected void onInactive() {
super.onInactive();
setValue(null);
}
@Override
public void observe(@NonNull LifecycleOwner owner, @NonNull Observer<? super T> observer) {
if (Objects.nonNull(getValue())) {
super.observe(owner, observer);
}
}
@Override
public void observeForever(@NonNull Observer<? super T> observer) {
if (Objects.nonNull(getValue())) {
super.observeForever(observer);
}
}
}