如何基于三个Flow状态实现多用户数据更新仅显示单个Toast
问题描述
在Android Kotlin项目中,UpdateUserInfoRepoImpl提供三个独立的更新方法(用户名、邮箱、头像),均返回MutableSharedFlow<Resource<Boolean>>;ViewModel对应维护三个SharedFlow状态;Fragment中分别收集这三个状态时,单个更新会弹出对应成功Toast,但同时更新多个数据时会依次弹出多个Toast。尝试用combine操作符仅能实现三个更新都成功时显示单个Toast,现在需要实现:无论更新1/2/3个数据,只要有成功更新就只显示一个“数据更新成功”的Toast。
解决方案1:ViewModel统一维护更新结果状态
步骤1:修改ViewModel,新增统一成功事件Flow
在ViewModel中添加统一的成功事件流,同时用原子标记避免短时间内重复触发Toast:
@HiltViewModel class UpdateUserInfoViewModel @Inject constructor( private val updateUserNameUseCase: UpdateUserNameUseCase, private val updateUserEmailUseCase: UpdateUserEmailUseCase, private val uploadUserProfileImageUseCase: UploadUserProfileImageUseCase, ) : ViewModel() { // 原有状态保留,用于加载/错误处理 private var _updateUserNameState = MutableSharedFlow<Resource<Boolean>>() val updateUserNameState = _updateUserNameState.asSharedFlow() private var _updateUserEmailState = MutableSharedFlow<Resource<Boolean>>() val updateUserEmailState = _updateUserEmailState.asSharedFlow() private var _updateUserProfileImageState = MutableStateFlow<Resource<Boolean>>(Resource.Ideal()) val updateUserProfileImageState = _updateUserProfileImageState.asSharedFlow() // 新增统一的更新成功事件Flow private val _anyUpdateSuccess = MutableSharedFlow<Unit>() val anyUpdateSuccess = _anyUpdateSuccess.asSharedFlow() private val toastFlag = AtomicBoolean(false) fun updateUserName(userName: String) { viewModelScope.launch { updateUserNameUseCase(userName).collect { response -> _updateUserNameState.emit(response) if (response is Resource.Success && response.data == true) { triggerSuccessEvent() } } } } fun updateUserEmail( oldEmail: String?, newEmail: String, password: String?, idToken: String? = null ) { viewModelScope.launch { updateUserEmailUseCase(oldEmail, newEmail, password, idToken).collect { response -> _updateUserEmailState.emit(response) if (response is Resource.Success && response.data == true) { triggerSuccessEvent() } } } } fun updateUserProfileImage(imageDataByteArray: ByteArray) { viewModelScope.launch { uploadUserProfileImageUseCase(imageDataByteArray).collect { response -> _updateUserProfileImageState.emit(response) if (response is Resource.Success && response.data == true) { triggerSuccessEvent() } } } } private fun triggerSuccessEvent() { viewModelScope.launch { // 原子标记确保2秒内只发送一次事件 if (toastFlag.compareAndSet(false, true)) { _anyUpdateSuccess.emit(Unit) delay(2000) toastFlag.set(false) } } } }
步骤2:修改Fragment,收集统一成功事件
移除原有三个状态收集逻辑中的Toast,仅保留加载/错误处理,新增统一成功事件的收集:
// 原有状态收集:保留加载、错误处理,移除Success分支的Toast lifecycleScope.launch { viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) { updateUserInfoViewModel.updateUserNameState.collect { response -> when (response) { is Resource.Loading -> loadingDialog.show() is Resource.Success -> { loadingDialog.cancel() mainActivityViewModel.getUserInfo() } is Resource.Error -> { loadingDialog.cancel() Log.e(TAG, "updateUserNameState: ${response.message.toString()}") } else -> {} } } } } lifecycleScope.launch { viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) { updateUserInfoViewModel.updateUserEmailState.collect { response -> when (response) { is Resource.Loading -> loadingDialog.show() is Resource.Success -> { loadingDialog.cancel() mainActivityViewModel.getUserInfo() } is Resource.Error -> { loadingDialog.cancel() Log.e(TAG, "updateUserEmailState: ${response.message.toString()}") } else -> loadingDialog.cancel() } } } } lifecycleScope.launch { viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) { updateUserInfoViewModel.updateUserProfileImageState.collect { response -> when (response) { is Resource.Loading -> loadingDialog.show() is Resource.Success -> { loadingDialog.cancel() mainActivityViewModel.getUserInfo() } is Resource.Error -> { loadingDialog.cancel() Log.e(TAG, "updateUserProfileImageState: ${response.message.toString()}") } else -> {} } } } } // 新增统一成功事件收集,显示单个Toast lifecycleScope.launch { viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) { updateUserInfoViewModel.anyUpdateSuccess.collect { Toast.makeText(requireContext(), "数据更新成功", Toast.LENGTH_SHORT).show() } } }
解决方案2:Fragment层面合并状态并节流
无需修改ViewModel,直接在Fragment中合并三个状态流,过滤成功事件并节流:
// 移除原有三个带Toast的收集逻辑,替换为以下代码 // 处理加载、错误状态 lifecycleScope.launch { viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) { merge( updateUserInfoViewModel.updateUserNameState, updateUserInfoViewModel.updateUserEmailState, updateUserInfoViewModel.updateUserProfileImageState ).collect { response -> when (response) { is Resource.Loading -> loadingDialog.show() is Resource.Error -> { loadingDialog.cancel() Log.e(TAG, "Update error: ${response.message.toString()}") } else -> {} } } } } // 合并成功事件并节流,确保2秒内只显示一次Toast lifecycleScope.launch { viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) { merge( updateUserInfoViewModel.updateUserNameState, updateUserInfoViewModel.updateUserEmailState, updateUserInfoViewModel.updateUserProfileImageState ) .filter { it is Resource.Success && it.data == true } .sample(2000) // 2秒内只处理一次事件 .collect { loadingDialog.cancel() Toast.makeText(requireContext(), "数据更新成功", Toast.LENGTH_SHORT).show() mainActivityViewModel.getUserInfo() } } }
方案说明
- 方案1:ViewModel层面统一管理成功事件,逻辑更清晰,适合复杂业务场景;
- 方案2:仅在Fragment层面对流进行合并处理,实现更轻量,适合快速调整;
- 两种方案均通过节流/原子标记机制,确保短时间内无论触发多少次成功事件,只弹出一次Toast。
内容的提问来源于stack exchange,提问作者Dr Mido
相关产品推荐
相关产品推荐

