Graziano Rizzi
Graziano Rizzi

Reputation: 160

How get result to UI Thread from an android kotlin coroutines

I didn't understand how kotlin coroutines work. I need to do a long work on an asynchronous thread and get the result on the UI Thread in an Android app. Can someone give me some examples? For example

private fun getCountries(){
        viewModelScope.launch { 
            val a = model.getAllCountries()
            countriesList.value = a
        }
}

will lunch model.getAllCountries() async but in the end how can i get result to UI Thread?

Upvotes: 10

Views: 13593

Answers (4)

Rachit Mishra
Rachit Mishra

Reputation: 6112

Well! Adding to @ianhanniballake's answer,

In your function,

private fun getCountries(){
   // 1
   viewModelScope.launch {  
      val a = model.getAllCountries()
      countriesList.value = a
   }
}
  1. You have launched your suspend function from viewModel scope, and the default context is the main thread.

Now the thread on which suspend fun getAllCountries will work will be specified in the definition of getAllCountries function.

So it can be written something like

suspend fun getAllCountries(): Countries {
   // 2
   return withContext(Dispatchers.IO) { 
    service.getCountries()
   }
}
  1. We specify a new thread to call the server using withContext, and after return from withContext block, we are back on main thread.

Upvotes: 8

Nicola Gallazzi
Nicola Gallazzi

Reputation: 8723

Another solution would be to post your result within a MutableLiveData inside your ViewModel class and observe the LiveData in your view.

Your ViewModel class:

class CountriesViewModel : ViewModel() {
    private val parentJob = Job()
    val coroutineContext: CoroutineContext
        get() = parentJob + Dispatchers.Default
    val viewModelScope = CoroutineScope(coroutineContext)
    val countries: MutableLiveData<ArrayList<Country>> = MutableLiveData()
    val model = MyModel()

    fun getCountries(){
        viewModelScope.launch {
            val countriesList = model.getAllCountries()
            countries.postValue(countries)
        }
    }
}

Your view class (E.g. a fragment)

class CountriesFragment : Fragment(){
        private lateinit var countriesVM : CountriesViewModel
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            countriesVM = ViewModelProviders.of(this).get(CountriesViewModel::class.java)
            // calling api in your view model here
            countriesVM.getCountries()
        }

        override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
            super.onViewCreated(view, savedInstanceState)
            // observer is notified of the changes on countries livedata
            countriesVM.countries.observe(this, Observer { countries ->
                // Update ui here
                updateUI(countries)
            })
        }
    }

Upvotes: 0

Marko Topolnik
Marko Topolnik

Reputation: 200296

I need to do a long work on an asynchronous thread

There's no such thing as an asynchronous thread, actually. Whether your network operations are sync or async gets decided by the implementation of the network API you're using.

If you have a blocking network operation, it will stay blocking even when you apply coroutines. The value of coroutines for that use case is limited to making it a bit easier to transfer the result back to the UI thread.

You achieve this by launching a coroutine with the UI dispatcher (the default) and then switching to a thread pool to perform a blocking operation without blocking the UI thread:

    viewModelScope.launch { 
        countriesList.value = withContext(Dispatchers.IO) {
            model.getAllCountries()
        }
    }

Note that a thread inside the thread pool underlying the IO dispatcher will still be blocked, so in terms of the usage of system resources this doesn't make a difference. There will be as many blocked native threads as there are concurrent network calls.

Upvotes: 3

ianhanniballake
ianhanniballake

Reputation: 200120

As per the documentation for viewModelScope:

This scope is bound to Dispatchers.Main.immediate

Where Dispatchers.Main is the Kotlin way of saying 'the main thread'. This means that, by default, all of the code in the launch block runs on the main thread. Your getAllCountries(), if it wants to run on a different thread, would want to use withContext(Disptachers.IO) to move to the IO coroutine dispatcher, as an example.

Therefore in this case, the result of your method is already on the main thread and there's nothing else you need to do.

Upvotes: 4

Related Questions