触发OnRefreshListener时清空MutableLiveData集合并避免空指针的方法
问题描述
我开发了一款用Retrofit从服务器拉取数据的应用,还实现了SwipeRefreshLayout让用户触发重新请求。现在遇到个问题:触发OnRefreshListener时需要清空存储服务器响应的MutableLiveData集合,试了把它设为null,但因为在onCreateView里观察这个LiveData并给Adapter传数据,每次刷新都会报NullPointerException。该怎么解决?要不要在触发OnRefreshListener时取消观察再重新观察响应集合?
ViewModel 代码
var responseData = MutableLiveData<Model?>() fun fetchData(baseCurrency: String, selectedCurrencies: String) { viewModelScope.launch { val response = retrofitRepository.fetchHistoricalData(date, selectedCurrencies, baseCurrency) response.enqueue(object : retrofit2.Callback<Model> { override fun onResponse( call: Call<HistoricalRatesModel>, response: Response<HistoricalRatesModel> ) { if (response.isSuccessful) { responseData.value = response.body() } } override fun onFailure(call: Call<HistoricalRatesModel>, t: Throwable) { Log.i(TAG, "onFailure ERROR\n${t.message}") } }) } }
Fragment 代码
override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View { _binding = FragmentHistoricalRatesBinding.inflate(inflater, container, false) val view = mBinding.root mViewModel.responseData.observe(requireActivity(), androidx.lifecycle.Observer { mAdapter = Adapter() mAdapter?.setData(it!!.rates) mBinding.hrv.layoutManager = LinearLayoutManager(this.context) mBinding.hrv.adapter = mAdapter }) mBinding.refreshContainer.setOnRefreshListener { mBinding.refreshContainer.isRefreshing = false } return view }
解决方案
1. 别把MutableLiveData设为null,用空有效对象替代
直接给responseData赋值null会导致Observer拿到null,触发it!!.rates的NPE。正确做法是刷新时先传一个空数据的Model对象,既清空列表,又避免null:
// ViewModel里修改 var responseData = MutableLiveData<Model>() // 初始化时先给空数据 init { responseData.value = Model(emptyMap()) } fun fetchData(baseCurrency: String, selectedCurrencies: String) { // 刷新第一步:先设置空数据,触发UI清空列表 responseData.value = Model(emptyMap()) viewModelScope.launch { val call = retrofitRepository.fetchHistoricalData(date, selectedCurrencies, baseCurrency) call.enqueue(object : retrofit2.Callback<Model> { override fun onResponse(call: Call<Model>, response: Response<Model>) { if (response.isSuccessful) { // 接口返回null的话,还是用空数据兜底 responseData.value = response.body() ?: Model(emptyMap()) } else { // 请求失败也保持空数据,避免UI崩溃 responseData.value = Model(emptyMap()) } // 或者加个错误状态通知UI } override fun onFailure(call: Call<Model>, t: Throwable) { Log.i(TAG, "onFailure ERROR\n${t.message}") responseData.value = Model(emptyMap()) } }) } }
2. 优化Fragment的Observer逻辑,避免重复初始化
现在的代码每次LiveData更新都新建Adapter、设置LayoutManager,既浪费性能又没必要。应该只初始化一次,后续只更新数据:
override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View { _binding = FragmentHistoricalRatesBinding.inflate(inflater, container, false) val view = binding.root // 初始化Adapter和LayoutManager,只做一次 mAdapter = Adapter() binding.hrv.layoutManager = LinearLayoutManager(context) binding.hrv.adapter = mAdapter // 观察LiveData,只更新数据 mViewModel.responseData.observe(viewLifecycleOwner) { model -> mAdapter.setData(model.rates) // 请求完成后关闭刷新状态 binding.refreshContainer.isRefreshing = false } binding.refreshContainer.setOnRefreshListener { // 发起重新请求,传入对应的参数 mViewModel.fetchData("需要的baseCurrency", "需要的selectedCurrencies") } return view }
3. 关键注意点
- 用
viewLifecycleOwner替代requireActivity()作为观察的生命周期所有者,避免Fragment销毁后仍持有Activity引用导致内存泄漏。 - 刷新状态不要在OnRefreshListener里直接关闭,要等请求完成(成功/失败)后再处理,给用户正确的刷新反馈。
- 永远不要依赖
!!强制非空,用?:做兜底处理,从根源避免NPE。
内容的提问来源于stack exchange,提问作者asdwq
相关产品推荐
相关产品推荐

