2

I have a function for the user's login. But it is suspended. I try to get its return value, but I can't. Here's what I tried to do

Code

class LoginViewModel @ViewModelInject constructor(private val remoteDataSource: OrderRemoteDataSource) :
    ViewModel() {

    private fun areValidCredentials(username: String?, password: String?): Boolean {
        return username != null && password != null && username.length > 4 && password.length > 4
    }

    suspend fun login(username: String?, password: String?): Boolean {
        return suspendCoroutine { it ->
            val valid = areValidCredentials(username, password)
            if (valid) {
                // call finish so login activity won't show up after back button clicked in home fragment
                try {
                    viewModelScope.launch {
                        //TODO CHECK if error code
                        val loginResponse =
                            remoteDataSource.login(LoginRequest(username!!, password!!))
                        if (loginResponse.status == Resource.Status.SUCCESS) {
                            val jwtToken = loginResponse.data?.jwtToken
                            if (!jwtToken.isNullOrEmpty()) {
                                sessionManager.saveAuthToken(jwtToken!!)
                                //ERROR!
                                it.resume(true)
                            }

                        }
                    }
                } catch (e: Exception) {
                    Log.i("[LoginActivity]", e.localizedMessage!!)
                    it.resume(false)
                    e.printStackTrace()
                }
            } else {
                Toast.makeText(
                    LOGIN_ACTIVITY,
                    "Username and password must be at least 5 letters long",
                    Toast.LENGTH_SHORT
                ).show()
            }
            it.resume(false)
        }
    }
}

And i call it

@AndroidEntryPoint
class LoginFragment : Fragment() {
    private val mViewModel: LoginViewModel by viewModels()
    private lateinit var navController: NavController

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View {
        return inflater.inflate(R.layout.frg_login, container, false)
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        Log.d("[LoginFragment]", "onCreate fun started!")

    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        navController = findNavController()
        loginButton.setOnClickListener {
            //TODO navigate to new fragmnet
            lifecycleScope.launch {
                mViewModel.login(
                    loginUsernameText.text.toString(),
                    loginPasswordText.text.toString()
                )
            }
        }
    }

And i have error

E/AndroidRuntime: FATAL EXCEPTION: main Process: ru.gkomega.navigation, PID: 11863 java.lang.IllegalStateException: Already resumed at kotlin.coroutines.SafeContinuation.resumeWith(SafeContinuationJvm.kt:45) at ru.gkomega.maumarket.ui.login.LoginViewModel$login$$inlined$suspendCoroutine$lambda$1.invokeSuspend(LoginViewModel.kt:40) at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33) at kotlinx.coroutines.DispatchedTask.run(Dispatched.kt:241) at android.os.Handler.handleCallback(Handler.java:938) at android.os.Handler.dispatchMessage(Handler.java:99) at android.os.Looper.loop(Looper.java:223) at android.app.ActivityThread.main(ActivityThread.java:7656) at java.lang.reflect.Method.invoke(Native Method) at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:592) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:947) I/chatty: uid=10163(ru.gkomega.navigation) identical 16 lines W/mega.navigatio: Got a deoptimization request on un-deoptimizable method java.lang.Class java.lang.Class.classForName(java.lang.String, boolean, java.lang.ClassLoader) I/Process: Sending signal. PID: 11863 SIG: 9 Disconnected from the target VM, address: 'localhost:58264', transport: 'socket'

I don't know much about coroutines so they're probably the problem

4
  • 1
    I think you have a bad aproach, remove the suspend from fun login and inside use viewModelScope.launch(Dispatchers.IO) and doesn´t return anything, create two livedata, one to notify the result success and another to notify the result error, or livedata to show a error feedback to try again, or whatever, but return nothing. Commented Dec 4, 2020 at 11:01
  • @ManuelMato I don't fully understand why I need so much LiveData. You can cite code fragment Commented Dec 4, 2020 at 11:07
  • @MehranBehbahani If i delete it how i get return value? Commented Dec 4, 2020 at 11:13
  • Because if you are using an architecture, the architecture must be respected, so the UI must be changed when the livedata changes and avoid logic in the UI. If you received true or false from viewmodel, you needs the logic: if (true ) else ... the UI only must be logic to print data because the Activity does a lot of things as permissions, navigations, etc. So this logic is better in the VM and your code will be simplified too... less code is than less bugs and it will be easiest to maintenain it. Commented Dec 4, 2020 at 12:11

2 Answers 2

1

You are resuming the coroutine regardless of the request happened or not, it failed or not.

suspend fun login(username: String?, password: String?): Boolean = suspendCoroutine { cont ->
    if (areValidCredentials(username, password)) {
        try {
            viewModelScope.launch {
                val loginResponse = remoteDataSource.login(LoginRequest(username!!, password!!))
                val jwtToken = loginResponse.data?.jwtToken
                if (loginResponse.status == Resource.Status.SUCCESS && !jwtToken.isNullOrEmpty()) {
                    sessionManager.saveAuthToken(jwtToken!!)
                    cont.resume(true)
                } else cont.resume(false)  // <-- Don't forget
            }
        } catch (e: Exception) {
            Log.i("[LoginActivity]", e.localizedMessage!!)
            cont.resume(false)
            e.printStackTrace()
        }
    } else {
        Toast.makeText(
            LOGIN_ACTIVITY,
            "Username and password must be at least 5 letters long",
            Toast.LENGTH_SHORT
        ).show()
        cont.resume(false)  // <-- Put it right here.
    }
    // cont.resume(false)  // <-- Not here
}
Sign up to request clarification or add additional context in comments.

Comments

1

Try this code!

override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState)

navController = findNavController()
loginButton.setOnClickListener {
    //TODO navigate to new fragment
        val isAuth = mViewModel.login(
            loginUsernameText.text.toString(),
            loginPasswordText.text.toString()
        )
        if (isAuth) {
            val startMainAction =
                LoginFragmentDirections.actionStartMain(loginUsernameText.text.toString())
            navController.navigate(startMainAction)
        }       
     }

}

And this fragment on viewModel

fun login(username: String?, password: String?): Boolean {
        var isAuth = false
        val valid = areValidCredentials(username, password)
        if (valid) {
            // call finish so login activity won't show up after back button clicked in home fragment
            try {
                //TODO CHECK if error code
                runBlocking {
                    val loginResponse = remoteDataSource.login(LoginRequest(username!!, password!!))
                    if (loginResponse.status == Resource.Status.SUCCESS) {
                        val jwtToken = loginResponse.data?.jwtToken.toString()
                        if (!jwtToken.isNullOrEmpty()) {
                            sessionManager.saveAuthToken(jwtToken)
                            isAuth = true
                        }
                    }
                }
            } catch (e: Exception) {
                Log.i("[LoginActivity]", e.localizedMessage!!)
                e.printStackTrace()
            }

I'm not sure if this code is correct, but it works!

1 Comment

Don't use blocking code or runBlocking{}, it is going to be running at the UI thread.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.