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

如何基于三个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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.13 17:20:54