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

Android Kotlin协程:等待协程完成后获取ViewModel变量问题

解决Kotlin协程异步导致ViewModel状态未及时更新的问题

你遇到的问题非常典型——协程的异步执行特性导致主线程提前读取了未更新的validEmail变量。sendAuthCode()里的数据库查询和邮件发送逻辑是在viewModelScope.launch开启的后台协程中运行的,而主线程调用完sendAuthCode()后会立刻执行后续的if(!validEmail)判断,这时候后台协程还没跑完,validEmail自然还是初始的false。

直接用join()等待协程完成是不可取的,因为如果在主线程调用join()会阻塞UI线程,导致界面卡顿甚至ANR(应用无响应)。下面给你两种符合Android架构规范的解决方案:

方案1:用LiveData/StateFlow观察状态变化(推荐)

ViewModel的核心职责之一是管理UI状态,直接暴露可变变量让Fragment同步读取不符合架构设计。更好的方式是用LiveData(或更现代的StateFlow)封装状态,让Fragment观察状态变化:

修改后的ViewModel代码

class ForgetPasswordViewModel(val repository: Repository) : ViewModel() {
    lateinit var emailAddress: String
    // 用MutableLiveData封装内部状态,对外暴露不可变的LiveData
    private val _validEmail = MutableLiveData<Boolean>()
    val validEmail: LiveData<Boolean> = _validEmail

    fun sendAuthCode() {
        viewModelScope.launch {
            val userData = repository.getUserDataByEmail(emailAddress)
            if (userData != null) {
                val authCode = userData.authCode
                val sender = SendMail(
                    AppContext.appContext!!,
                    emailAddress,
                    "Password reset code",
                    "Dear ${userData.firstName},\n\n" +
                    " This is your reset code.\n\n" +
                    "Password reset code: $authCode \n"
                )
                sender.execute()
                // 更新LiveData状态,自动通知观察者
                _validEmail.value = true
            } else {
                // 处理邮箱不存在的情况,更新状态
                _validEmail.value = false
            }
        }
    }
}

修改后的Fragment代码

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    super.onViewCreated(view, savedInstanceState)
    
    // 观察validEmail的状态变化,在状态更新时处理UI逻辑
    forgetPasswordViewModel.validEmail.observe(viewLifecycleOwner) { isValid ->
        if (isValid) {
            Toast.makeText(context, "Password reset code sent successfully!", Toast.LENGTH_SHORT).show()
        } else {
            Toast.makeText(context, "Email not found in the system", Toast.LENGTH_SHORT).show()
        }
    }

    viewBinding.btnRequestPassword.setOnClickListener {
        val email = emailAddress.text.toString()
        forgetPasswordViewModel.emailAddress = email
        if (email.length < 3) {
            ValidationChecks.showToast("Enter valid email address", context)
        } else {
            // 只触发发送逻辑,UI反馈交给观察者处理
            forgetPasswordViewModel.sendAuthCode()
        }
    }
}

方案2:将sendAuthCode改为挂起函数,在Fragment协程中调用

如果你更倾向于同步式的代码写法,可以把sendAuthCode改成挂起函数,然后在Fragment的lifecycleScope协程中调用,等待协程完成后再处理UI:

修改后的ViewModel代码

class ForgetPasswordViewModel(val repository: Repository) : ViewModel() {
    lateinit var emailAddress: String

    // 改成挂起函数,直接返回操作结果
    suspend fun sendAuthCode(): Boolean {
        val userData = repository.getUserDataByEmail(emailAddress)
        return if (userData != null) {
            val authCode = userData.authCode
            val sender = SendMail(
                AppContext.appContext!!,
                emailAddress,
                "Password reset code",
                "Dear ${userData.firstName},\n\n" +
                " This is your reset code.\n\n" +
                "Password reset code: $authCode \n"
            )
            sender.execute()
            true
        } else {
            false
        }
    }
}

修改后的Fragment代码

viewBinding.btnRequestPassword.setOnClickListener {
    val email = emailAddress.text.toString()
    forgetPasswordViewModel.emailAddress = email
    if (email.length < 3) {
        ValidationChecks.showToast("Enter valid email address", context)
    } else {
        // 在Fragment的生命周期协程中调用挂起函数,等待结果返回
        lifecycleScope.launch {
            val isSuccess = forgetPasswordViewModel.sendAuthCode()
            if (isSuccess) {
                Toast.makeText(context, "Password reset code sent successfully!", Toast.LENGTH_SHORT).show()
            } else {
                Toast.makeText(context, "Email not found in the system", Toast.LENGTH_SHORT).show()
            }
        }
    }
}

额外优化点

你ViewModel里存在一个冗余问题:已经通过构造函数注入了repository,但又在内部创建了val repo = Repository(),建议统一使用构造函数传入的实例,避免重复创建Repository对象。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.11 08:15:23