android: LiveData的一点注意事项

  • 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。明显,这样也不符合预期,而且更不好了。

后面,我想了两种可行的解决方案:

  1. 这个数据不要用 LiveData去保存,用普通变量boolean去保存即可。然后通过fragment.setArgments() 这个方法去传递。如果是用户点击就传递 false 或者不传;如果是收到WIFI断开的监听就传递一个true过去。这样每次 PassiveFragment.newInstance(disconnected);就能保证数据是正常的。
  2. 还是使用LiveData,拿到数据后显示,然后就粗暴地调用LiveData#setValue(null);只是,每次拿数据的时候先做一个非空判断。

当然,感觉还是第一种更好。更直观。

==========

然后,如果是使用第一种方案,就需要解决另外一个问题,就是如何在你的WifiReceiver里面获取FragmentManager/Activity 这些,不然,没办法进行 Fragment切换的操作了。

如何解决这个衍生的问题?

说一下我想到的解决方案:

  1. 把这个监听作为你的Activity的内部类,这样就解决这个问题了;
  2. 通过二次回调把这个状态改变的数据回调到你的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);
        }
    }
}

你可能感兴趣的:(android)