简体   繁体   中英

How can I get data from ViewModel to Activity or Fragment in clean and simple way?

I have a question... sometimes, I need to get data from ViewModel directly. For example, Let's say there's a isChecked() method in ViewModel. And I want to use it in the if condition.

if(viewModel.isChecked()){
    // TODO:
}

So, what I am doing right now is:

fun isChecked(): Boolean = runBlocking {
    val result = dbRepo.getData()
    val response = apiRepo.check(result)
    return response.isSuccessful
}

It uses runBlocking. So, it runs on MainThread. I don't think it's a good way because it can freeze the screen. But yes, if the condition needs to run, it needs to wait it until it gets the data from DB and Network.

Another way that I can think of is using LiveData. However, I can't use it in the condition. So, I needs to move the condition in the observer block. But sometimes, this can't be done because there can be something before the condition. And it doesn't seem to look direct but writing code here and there and finally get that data.

So, Is there any simpler way than this?

Use lifecyclescope.launch(Dispatcher.IO) instead of runblocking

Your best bet if you have something asynchronous like that is to rethink how you are using the data entirely. Instead of trying to return it, use LiveData or callbacks to handle the response asynchronously without causing your UI to hang or become laggy.

Callback to get state

It's hard to say definitely what the best solution for you is without more details about how you are using isChecked() , but one pattern that could work would be to use a callback to handle what you were formerly putting in the if statement, like this (in the ViewModel):

fun getCheckedState(callback: (Boolean)->Unit) {
    viewModelScope.launch {
        // do long-running task to get checked state,
        // using an appropriate dispatcher if needed
        val result = dbRepo.getData()
        val response = apiRepo.check(result)

        // pass "response.isSuccessful" to the callback, to be
        // used as "isChecked" below
        callback(response.isSuccessful)
    }
}

You would call that from the activity or fragment like this:

viewModel.getCheckedState { isChecked ->
  if( isChecked ) {
    // do something
  }
  else {
    // do something else
  }
}

// CAUTION: Do NOT try to use variables you set inside 
// the callback out here!

A word of caution - the code inside the callback you pass to getCheckedState does not run right away. Do not try to use things you set inside there outside the callback scope or you fall into this common issue

Simpler Callback

Alternately, if you only want to run some code when isChecked is true, you could simplify the callback like this

fun runIfChecked(callback: ()->Unit) {
    viewModelScope.launch {
        // do long-running task to get checked state,
        // using an appropriate dispatcher if needed
        val result = dbRepo.getData()
        val response = apiRepo.check(result)

        // pass "response.isSuccessful" to the callback, to be
        // used as "isChecked" below
        if( response.isSuccessful ) {
            callback()
        }
    }
}

and call it with

viewModel.runIfChecked {
  // do something
}

// Again, don't try to use things from the callback out here!

Try this code on your ViewModel class:

suspend fun isChecked(): Boolean {
    val response: Response? = null
    val job = viewModelScope.launch(Dispatchers.IO) {
        response = dbRepo.getData()
    }
    job.join()
    return response.isSuccessful
}

From Activity:

viewModel.viewModelScope.launch(Dispatchers.IO) {
    if (viewModel.isChecked()) {
        // Do your staff
    }
}

Hope it work file. If no let me comment

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