You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

触发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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.01 03:20:52