SNM
SNM

Reputation: 6785

How to return asynchronous operation from Coroutine to ViewModel

I'm trying to understand this last step.

I need an atomic one shot operation, I need to go to Firebase and place my user device token.

For this I just make a call to a method in my viewModel which will trigger my repo, but now, I dont want to use LiveData in my repo, instead, I want to just return Resource from there, but since is an asynchronous method I cant just return.

I want only to use LiveData at my viewModel nad not in my repository, the repository should only deliver objects to my viewModel and my viewModel should deliver these to my view.

View

viewModel.userToken.observe(this, Observer {
            when (it.status) {
                Status.SUCCESS -> {
                    val user = FirebaseAuth.getInstance().currentUser
                    startActivity(Intent(this, SecondActivity::class.java))
                    Toast.makeText(this, "Welcome ${user!!.uid} !", Toast.LENGTH_SHORT).show()
                    finish()
                }

                Status.ERROR -> {
                }

                else -> {
                    Toast.makeText(this, "error ${it.message}", Toast.LENGTH_SHORT).show()
                }
            }
        })

ViewModel

class LoginViewModel: ViewModel() {

    private val useCase = PostUserToken(UserRepo())
    var userToken = liveData(Dispatchers.IO){
        emit(useCase.postUserToken())
    }

}

Untill here, its working fine, now, from my postUserToken() method in my repo, I need to return a Resource<Boolean> object to my viewmodel, how do I do this with coroutines ?

UseCase

class PostUserToken(private val repo: UserRepo) {

    suspend fun postUserToken(): Resource<Boolean> = repo.saveUserToken()

}

Repo

class UserRepo {

    suspend fun saveUserToken(): Resource<Boolean> {
        FirebaseInstanceId.getInstance().instanceId
            .addOnCompleteListener(OnCompleteListener { task ->
                if (!task.isSuccessful) {
                    Log.w("saveUserToken", "getInstanceId failed", task.exception)
                    return@OnCompleteListener
                }

                // Get new Instance ID token
                val token = task.result?.token

            })

        //Here I need to return the Resource<Boolean> but wait untill it completes
    }
}

These are my two helper classes I use

Resource

data class Resource<out T>(val status: Status, val data: T?, val message: String?) {

    companion object {
        fun <T> success(data: T?): Resource<T> {
            return Resource(Status.SUCCESS, data, null)
        }

        fun <T> error(msg: String, data: T?): Resource<T> {
            return Resource(Status.ERROR, data, msg)
        }

        fun <T> loading(data: T?): Resource<T> {
            return Resource(Status.LOADING, data, null)
        }
    }
}

Status

enum class Status {
    SUCCESS,
    ERROR,
    LOADING
}

I'm always stuck at the repository when need to deliver objects to the fellow classes, which is the best approach to deliver this flow ?

I know that if I need to keep listening I should use Flow with Coroutines, but now I only need a one time operation that should deliver this object accross all classes to my view

Thanks

Upvotes: 1

Views: 1941

Answers (2)

AndroidZen
AndroidZen

Reputation: 606

You can call login from Activity or Fragment without any problem, then you should update your MutableLiveData to make your Activity aware of the changes.

What I've done to retrieve login from user repository and Firebase Auth is this:

ClassViewModel.kt

class LoginFirebaseViewModel(): ViewModel(){
    private val _loginResult = MutableLiveData<LoginResult>()
    val loginResult: LiveData<LoginResult> = _loginResult

    fun login() {
        viewModelScope.launch {
            try {
                repository.userLogin(email!!,password!!).let {
                    _loginResult.value = it
                }
            } catch (e: FirebaseAuthException) {
                // Do something on firebase exception
            }       
        }
    }
}

UserRepository.kt

class UserRepository(private val firebaseAuth: FirebaseAuth) {

    suspend fun userLogin(email: String, password: String) : LoginResult{
        val firebaseUser = firebaseAuth.signInWithEmailAndPassword(email, password).await()  // Do not forget .await()
        return LoginResult(firebaseUser)
    }
}

LoginResult is a wrapper class of firebase auth response.

I hope this help you

Upvotes: 1

Doug Stevenson
Doug Stevenson

Reputation: 317332

It's perfectly valid for a repository object to return a LiveData. That's what I would do. I don't think you should should try to make it return anything synchronously, as that defeats the purpose of coroutines entirely.

If you want to use a Task with coroutines, look into using this library that converts play services Task objects into something that can be awaited in a suspend fun:

https://github.com/Kotlin/kotlinx.coroutines/tree/master/integration/kotlinx-coroutines-play-services

Upvotes: 4

Related Questions