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

屏幕旋转时Toast反复弹出,Event包装LiveData方案问题排查

用Event类处理LiveData一次性事件的问题排查与解决

我尝试用Event类包装LiveData处理一次性事件:在Activity的onCreate中调用API,观察ViewModel的LiveData变量,在观察者里显示Toast消息。但屏幕旋转时Toast会持续弹出,想知道Event类是否是可行方案?如果可行,我哪里遗漏了?

相关代码

Event类实现

open class Event<out T>(private val content: T) {

    var hasBeenHandled = false
        private set // 允许外部读取但不能修改

    /**
     * 返回内容并标记为已处理,防止重复使用
     */
    fun getContentIfNotHandled(): T? {
        return if (hasBeenHandled) {
            null
        } else {
            hasBeenHandled = true
            content
        }
    }

    /**
     * 返回内容,即使已经被处理过
     */
    fun peekContent(): T = content
}

CustomActivity代码

class CustomActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        // 省略视图绑定等初始化代码
        viewModel.getTownsList(InputModel(1))
        bindObservers()
    }

    private fun bindObservers() {
        viewModel.townsList.observe(this) { event ->
            event.getContentIfNotHandled()?.let {
                when (it) {
                    is NetworkResult.Error ->
                        Toast.makeText(this, "Error", Toast.LENGTH_SHORT)
                            .show()

                    is NetworkResult.Loading ->
                        Toast.makeText(this, "Loading", Toast.LENGTH_SHORT)
                            .show()

                    is NetworkResult.Success -> {
                        Toast.makeText(this, "Success", Toast.LENGTH_SHORT).show()
                        townsAdapter.townsList = it.data!!
                    }
                }
            }
        }
    }
}

TownViewModel代码

@HiltViewModel
class TownViewModel @Inject constructor(private val townRepository: TownRepository): ViewModel() {

    private var _townsList = MutableLiveData<Event<NetworkResult<List<Towns>>>>()
    val townsList get() = _townsList

    fun getTownsList(inputModel: InputModel) = viewModelScope.launch (Dispatchers.IO) {
        _townsList.postValue(Event(NetworkResult.Loading()))
        val response = townRepository.getTowns(inputModel)
        if (response.isSuccessful && response.body() != null){
            _townsList.postValue(Event(NetworkResult.Success(response.body()!!)))
        }
        else if (response.errorBody() != null){
            _townsList.postValue(Event(NetworkResult.Error(response.message())))
        }
    }
}

问题原因分析

Event类本身是处理LiveData一次性事件的可行方案,你碰到的问题核心原因是:

  • 重复发起API请求:屏幕旋转后Activity会重建,onCreate()会再次执行,你在其中直接调用了viewModel.getTownsList(InputModel(1)),导致ViewModel重新发起API请求,再次生成新的Event实例(Loading/Success/Error)。这些新Event的hasBeenHandled初始值都是false,所以观察者会再次处理并弹出Toast。

  • 至于LiveData的重发特性:即使不重复调用API,Activity重建时LiveData会把最新值重发给新观察者,但如果是同一个Event实例,第一次处理后hasBeenHandled已经设为true,getContentIfNotHandled()会返回null,不会重复触发Toast——这不是你当前问题的核心。

解决方案

1. 避免重复发起API请求

在ViewModel中添加状态标记,记录数据是否已经加载完成,只有未加载时才发起请求:

@HiltViewModel
class TownViewModel @Inject constructor(private val townRepository: TownRepository): ViewModel() {
    private var _townsList = MutableLiveData<Event<NetworkResult<List<Towns>>>>()
    val townsList get() = _townsList
    // 添加数据加载状态标记
    private var isDataLoaded = false

    fun getTownsList(inputModel: InputModel) = viewModelScope.launch (Dispatchers.IO) {
        // 已加载过数据则直接返回,不再发起请求
        if (isDataLoaded) return@launch
        
        _townsList.postValue(Event(NetworkResult.Loading()))
        val response = townRepository.getTowns(inputModel)
        if (response.isSuccessful && response.body() != null){
            _townsList.postValue(Event(NetworkResult.Success(response.body()!!)))
            isDataLoaded = true // 请求成功后标记为已加载
        }
        else if (response.errorBody() != null){
            _townsList.postValue(Event(NetworkResult.Error(response.message())))
            // 如果需要允许用户重试请求,可以不设置isDataLoaded为true
            // isDataLoaded = false
        }
    }
}

2. 保持Event类的正确使用

你的Event类实现是正确的,只要不重复生成新的Event实例,Activity重建时观察者拿到的是之前处理过的Event,getContentIfNotHandled()会返回null,不会重复弹出Toast。你Activity中的观察者逻辑已经正确处理了未处理的Event,无需修改。

内容的提问来源于stack exchange,提问作者skv1996

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.06 04:24:55