简体   繁体   中英

Retrieving ViewModel variable after a coroutine has updated the variable and the coroutine is completed, Android Kotlin Coroutines

I am developing an application for android in Kotlin and writing the reset password fragment where the logic is as follows:

User enters email address to reset password. After the email is entered and 'Send reset code' is pressed, the ViewModel function forgetPasswordViewModel.sendAuthCode() is triggered which sends a query through repository to the Room database to check if the user data including email address exists in the room database, if that email address exist which is indicated by not null result, the system retrieves the reset code for the password (authCode) and emails it to the user. If the email address doesn't exist in the system (indicated by null userData), a boolean variable validEmail in the view model is set to true . This helps me to generate appropriate toast message in the ForgetPassword fragment.

I call the method forgetPasswordViewModel.sendAuthCode() in the ForgetPassword fragment and after that check validEmail variable that would indicate if the email already exists in the system, But the variable validEmail always return false because the coroutine forgetPasswordViewModel.sendAuthCode() is still running in the background thread and does not update the boolean variable validEmailyet uptil that point, and therefore on the main thread where fragment is running the code carries on running the following statements that retrieves the validEmail from ViewModel which is still false at at that point because the coroutine function forgetPasswordViewModel.sendAuthCode() has not set the variable to true yet.

Is there any function in Kotlin which I can use so that my code will wait until the coroutine has finished executing and updated the variable before my code continues executing. I have seen some solutions here on StackOverFlow where people have advised to use join() but I have only one coroutine and the Boolean variable validEmail is ViewModel scope variable. Can anyone kindly guide me in the right direction. Here is my code:

VIEW MODEL:

class ForgetPasswordViewModel(val repository: Repository) : ViewModel() {

    //Variable to contain user email address
    lateinit var emailAddress: String
    var validEmail: Boolean = false


    //Getting instance of Repository
    val repo = Repository()

    //Send Password reset code to email

    fun sendAuthCode() {
        viewModelScope.launch {
            val userData = repo.getUserDataByEmail(emailAddress)
            if (userData != null) {
                val authCode = userData.authCode
                //Send email to new user with account details
                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()
                validEmail = true //here I set it to true to indicate that things went well
            }
        }
    }
}

Here is the snippet from the ForgetPassword Fragment:

 // Set onClickListener to btnRequestPassword button
    viewBinding.btnRequestPassword.setOnClickListener{
        forgetPasswordViewModel.emailAddress = emailAddress.text.toString()
        if(forgetPasswordViewModel.emailAddress.length < 3) { //The theoretical minimum length of any email address is 3
            ValidationChecks.showToast("Enter valid email address",context)
        } else{
            forgetPasswordViewModel.sendAuthCode()
            if(!forgetPasswordViewModel.validEmail){
                Toast.makeText(context, "Email not found in the system",Toast.LENGTH_SHORT)
            } else {
                Toast.makeText(context, "Password reset code sent successfully!",Toast.LENGTH_SHORT)
            }

I will appreciate if anyone can nudge me in the right direction. Kind regards, Salik

Since you're expecting a synchronous behavior, you can start the coroutine in your fragment and make your sendAuthCode function suspending.

Fragment:

...
viewLifecycleOwner.lifecycleScope.launch {
    val validEmail = forgetPasswordViewModel.sendAuthCode()
    if(!validEmail){
        Toast.makeText(context, "Email not found in the system",Toast.LENGTH_SHORT)
    } else {
        Toast.makeText(context, "Password reset code sent successfully!",Toast.LENGTH_SHORT)
    }
}
...

ViewModel:

suspend fun sendAuthCode(): Boolean {
    val userData = repo.getUserDataByEmail(emailAddress)
    if (userData != null) {
        val authCode = userData.authCode
        //Send email to new user with account details
        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()
        return true
    }

    return false
}

Make sure your DAO methods are suspending so they run off the main thread. Or change the coroutine context if there is a blocking call in the middle of the operation because viewLifecycleOwner.lifecycleScope.launch {...} runs on the main thread. Something like this would do:

suspend fun sendAuthCode(): Boolean = withContext(Dispatchers.IO) {
    ...
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM