Search code examples
androidkotlinfirebase-cloud-messagingkotlin-coroutinesandroid-mvvm

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


Solution

  • 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