Reputation: 160
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
Reputation: 6112
Well! Adding to @ianhanniballake's answer,
In your function,
private fun getCountries(){
// 1
viewModelScope.launch {
val a = model.getAllCountries()
countriesList.value = a
}
}
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()
}
}
withContext
, and after return from withContext
block, we are back on main thread.Upvotes: 8
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
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
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