屏幕旋转时Toast反复弹出,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

