Is it possible in Kotlin to call some code from the main thread that blocks until that code completes but at the same time does not block the actual UI? From my understanding, coroutines might be able to do that through a process that "suspends" executing code on the main thread until the other thread completes. But I have no idea how to do that if it is even possible.
Coroutines are pretty easy. Import them:
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.2.2'
Make your first async code:
private val completableJob = Job()
private val coroutineScope = CoroutineScope(Dispatchers.IO + completableJob)
//you need these because you need to handle them with Android Lifecycle.
fun fireACoroutine(){
corotutineScope.launch(Dispatchers.IO){
val someDataFromSuspendFunction = getStringAfterDelay()
withContext(Dispatchers.Main){
someTextView.text = someDataFromSuspendFunction
//switched to Main Thread.
}
}
var x = 0
x++ //you are outside of a coroutine
}
suspend fun getStringAfterDelay(): String = withContext(Dispatchers.IO){
delay(1000)
"Hello World"
}
Don't forget the to clean your hands when in Activity/Fragment:
override fun onDestroy() {
super.onDestroy()
completableJob.cancel()
}
Done! I also have an article about coroutines: Kotlin Coroutines, the non confusing one
I also suggest to know more about operators like launch, async or runBlocking.
Alternatively to coroutines, you may use RxJava to do what you request in questions. It does work in Kotlin like charm. Also, if you feel like you need to know more about both of them, stick to AsyncTask.
Related
While I was learning coroutines and how to properly use them in an android app I found something I was surprised about.
When launching a coroutine using viewModelScope.launch { } and setting a breakpoint inside the launch lambda I noticed my app wasn't responsive anymore because it was still on the main thread.
This confuses me because the docs of viewModelScope.launch { } clearly state:
Launches a new coroutine without blocking the current thread
Isn't the current thread the main thread ? What is the whole purpose of launch if it doesn't run on a different thread by default ?
I was able to run it on anther thread using viewModelScope.launch(Dispatchers.IO){ } which works as I was expecting, namely on another thread.
What I am trying to accomplish from the launch method is to call a repository and do some IO work namely call a webservice and store the data in a room db. So I was thinking of calling viewModelScope.launch(Dispatchers.IO){ } do all the work on a different thread and in the end update the LiveData result.
viewModelScope.launch(Dispatchers.IO){
liveData.postValue(someRepository.someWork())
}
So my second question is, is this the way to go ?
ViewModelScope.launch { } runs on the main thread, but also gives you the option to run other dispatchers, so you can have UI & Background operations running synchronously.
For you example:
fun thisWillRunOnMainThread() {
viewModelScope.launch {
//below code will run on UI thread.
showLoadingOnUI()
//using withContext() you can run a block of code on different dispatcher
val result = novel.id = withContext(Dispatchers.IO) {
withsomeRepository.someWork()
}
//The below code waits until the above block is executed and the result is set.
liveData.value = result
finishLoadingOnUI()
}
}
For more reference, I would say there are some neat articles that will help you understand this concept.
Medium link that explains it really neat.
So my second question is, is this the way to go ?
I would expect two things to be different in your current approach.
1.) First step would be to define the scheduler of the background operation via withContext.
class SomeRepository {
suspend fun doWork(): SomeResult = withContext(Dispatchers.IO) {
...
}
}
This way, the operation itself runs on a background thread, but you didn't force your original scope to be "off-thread".
2.) Jetpack Lifecycle KTX provides the liveData { coroutine builder so that you don't have to postValue to it manually.
val liveData: LiveData<SomeResult> = liveData {
emit(someRepository.someWork())
}
Which in a ViewModel, you would use like so:
val liveData: LiveData<SomeResult> = liveData(context = viewModelScope.coroutineContext) {
withContext(Dispatchers.IO) {
emit(someRepository.someWork())
}
}
And now you can automatically trigger data-loading via observing, and not having to manually invoke viewModelScope.launch {}.
The idea behind main thread being default is you can run UI operations without having to change the context. It is a convention I guess Kotlin coroutine library writers have chosen
Suppose if by default if the launch runs on IO thread then the code would look like this
viewmodelScope.launch {
val response = networkRequest()
withContext(Dispatchers.Main) {
renderUI(response)
}
}
Suppose if by default if the launch runs on Default thread then the code would look like this
viewmodelScope.launch {
val response: Response = null
withContext(Dispatchers.IO) {
response = networkRequest()
}
withContext(Dispatchers.Main) {
renderUI(response)
}
}
Since the default launch is on main thread, now you have to do below
viewmodelScope.launch {
val response: Response = null
withContext(Dispatchers.IO) {
response = networkRequest()
}
renderUI(response)
}
To avoid the messy code initializing the response with null, we can also make the networkRequest as suspend and wrap the business logic of networkRequest() function in withContext(Dispatchers.IO) and that's how lot of people write their networkRequest() function as well! Hope this makes sense
One of the main reasons it runs on Main thread, is because it's more practical for general use in ViewModel, like murali kurapati wrote. It was a design choice.
It's also important to note that all suspending functions should be "main safe" according to best pracices. That means, that your repository should switch coroutine context accordingly, like so:
class someRepository(private val ioDispatcher: CoroutineDispatcher) {
suspend fun someWork() {
withContext(ioDispatcher) {
TODO("Heavy lifting")
}
}
}
I have a method
fun refrehList() {
viewModelScope.launch {
myData.value = withContext(Dispatchers.Default) {
summaryRepository.getSummaries(true)
}
allData.value = withContext(Dispatchers.Default) {
SummaryRepository.getSummaries(false)
}
}
}
Is this correct way of using coroutine. Is the DB operation happening in the background scope
If you're using Room, its documentation states the following:
You can add the suspend Kotlin keyword to your DAO methods to make
them asynchronous using Kotlin coroutines functionality. This ensures
that they cannot be executed on the main thread.
So you will be safe calling your repository inside the viewModelScope without changing context.
You can find that Room's documentation section here.
Yes this code will run on separate thread but one after another. Also you should be using Dispatchers.IO for database calls instead of Dispatchers.Default See Io vs Default.
viewModelScope.launch {
myData.value = withContext(Dispatchers.IO) {
Log.e("thread1", Thread.currentThread().name)
summaryRepository.getSummaries(true)
}
Log.e("thread2", Thread.currentThread().name)
allData.value = withContext(Dispatchers.IO) {
Log.e("thread3", Thread.currentThread().name)
SummaryRepository.getSummaries(false)
}
}
This will print something like :-
E/thread: DefaultDispatcher-worker-1
E/thread2: main
E/thread3: DefaultDispatcher-worker-1
If you want to run those co-routine in parallel you can use async-await .
I have switched my context to Dispatcher.Main to show UI but data fetched on runBlocking but unable to display in RecylerView
runBlocking {
var fruits = fetchFruitList()
withContext(Dispatchers.Main){
recyclerView.adapter = FruitAdapter(fruits);
}
}
what am I doing wrong and what is the appropriate way to return data from one Dispatcher to another.
I have tried another way
GlobalScope.launch {
withContext(Dispatchers.IO){
var fruits = arrayOf("Grapes","Apple","Mango","TuttiFruit","PineApple",
"Pomegrante","Apple","Mango","TuttiFruit","PineApple",
"Pomegrante","Apple","Mango","TuttiFruit","PineApple").toList()
return#withContext
}
recyclerView.adapter = FruitAdapter(fruits)
}
but in above way I have to declare fruits as global whereas I don't want to have it global to work. Is there a way to return data from one 'dispatcher queue to another
I have to fetch data from Api (IO operation) and display that data in RecyclerView(Main Thread Operation)
That's because you must switch the context after the data is fetched:
GlobalScope.launch(Dispatchers.IO){
var fruits = arrayOf("Grapes","Apple","Mango","TuttiFruit","PineApple",
"Pomegrante","Apple","Mango","TuttiFruit","PineApple",
"Pomegrante","Apple","Mango","TuttiFruit","PineApple").toList()
withContext(Dispatchers.MAIN){
recyclerView.adapter = FruitAdapter(fruits)
}
}
Edit According to the comments:
For the runBlocking check out the documentation first paragraph.
Runs a new coroutine and blocks the current thread interruptibly until
its completion. This function should not be used from a coroutine. It
is designed to bridge regular blocking code to libraries that are
written in suspending style, to be used in main functions and in
tests.
Secondly, you ask for the GlobalScope usage. Yea, if you are doing coroutines in Android you should avoid that. Reasons here.
How to launch a coroutine in Android Activity/Fragment?
First I suggest to use in the ViewModel or the Presenter but if you want to launch a coroutine in the Activity/Fragment, you will need a way to control and manage it's cancellation to avoid memory leak.
The solution for this would be:
private val job: Job = Job()
//or Dispatchers.IO
private val fragmentScope = CoroutineScope(Dispatchers.MAIN + job)
//launch a coroutine later in Activity/Fragment
fragmentScope.launch{
//the default coroutine dispatcher would be the defined dispatcher above
}
override fun onDestroy(){
super.onDestroy()
fragmentScope.cancel()
}
As for your question:
what am I doing wrong and what is the appropriate way to return data
from one Dispatcher to another
You can also try this solution if you want to return values from a different context:
someScope.launch(Dispatchers.MAIN){
var data = withContext(Dispatchers.IO){
val someData = fetchSomeData()
return#withContext data
}
if(data.isAvailable()){ //for example
//runing on the main thread
}
}
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?
Well! Adding to #ianhanniballake's answer,
In your function,
private fun getCountries(){
// 1
viewModelScope.launch {
val a = model.getAllCountries()
countriesList.value = a
}
}
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(Disptachers.IO) {
service.getCountries()
}
}
We specify a new thread to call the server using withContext, and after return from withContext block, we are back on main thread.
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.
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.
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)
})
}
}
I am using a callback function when async ends. but it doesn't work well :(
my case:
fun function1(callback : (obj1: List<ObjT1>,obj2: List<ObjT1>)-> Unit?){
doAsync {
//long task
uiThread { callback(result1, result2) }
}
}
the callback is called but result1 and result2(lists) are empty. I checked previous the content of the list.
EDIT:
PROBLEM: my callback is a function that receives 2 objects result 1 and result2, the problem is the function callback sometimes receives the results empty, i check their content and is not empty.
It may be because you've declared return type as Unit? but are returning two values. A quick fix would be to put result1 and result2 in an array.
Now this question is about deprecated Kotlin library.
I recommend use coroutines.
Consider using Kotlin's coroutines. Coroutines is a newer feature in Kotlin. It is still technically in it's experimental phase, but JetBrains has told us that it is very stable.
Read more here: https://kotlinlang.org/docs/reference/coroutines.html
Here is some sample code:
fun main(args: Array<String>) = runBlocking { // runBlocking is only needed here because I am calling join below
val job = launch(UI) { // The launch function allows you to execute suspended functions
val async1 = doSomethingAsync(250)
val async2 = doSomethingAsync(50)
println(async1.await() + async2.await()) // The code within launch will
// be paused here until both async1 and async2 have finished
}
job.join() // Just wait for the coroutines to finish before stopping the program
}
// Note: this is a suspended function (which means it can be "paused")
suspend fun doSomethingAsync(param1: Long) = async {
delay(param1) // pause this "thread" (not really a thread)
println(param1)
return#async param1 * 2 // return twice the input... just for fun
}