I spent some time to find a developer friendly solution (without adding dependencies to the project) of how to perform some hard task in background thread and after the task is completed return result to main thread. I found "AsyncTask" which allows to do that. But to use it you need to write boilerplate code for each task you need to run in Background. I am iOS developer who decided to try Android-related developing. So in Swift you can simply use the next code to make this task:
DispatchQueue.global().async(execute: {
//Do some hard task in background
DispatchQueue.main.async(execute: {
//Return to main
})
})
This looks pretty simple. But in Kotlin I didn't find such simple solution and decided to create it.
Here is what I made:
I created Generic class
import android.os.AsyncTask
class BaseAsyncTask<M>: AsyncTask<()->M, Int, M>() {
var completion: ((M)->Unit)? = null
override fun doInBackground(vararg params: (() -> M)?): M? {
for (p in params) {
return p?.invoke()
}
return null
}
override fun onPostExecute(result: M) {
super.onPostExecute(result)
completion?.invoke(result)
}
}
And Manager
class AsyncManager {
companion object {
fun <M>execute(inBackground: ()->M, inMain: (M)->Unit): BaseAsyncTask<M> {
val task = BaseAsyncTask<M>()
task.completion = inMain
task.execute(inBackground)
return task
}
fun <M>execute(inBackground: ()->M): BaseAsyncTask<M> {
val task = BaseAsyncTask<M>()
task.execute(inBackground)
return task
}
}
}
Now I use it like this:
AsyncManager.execute({
//Do some hard task in background
}, {
//Return to main
})
Looks developer friendly.
Log.e("MAIN", "MAIN THREAD SHOULD NOT BE BLOCKED")
AsyncManager.execute({
Log.e("TASK", "Started background task")
val retval = "The value from background"
Thread.sleep(5000)
Log.e("TASK", "Finished background task with result: " + retval)
retval
}, {
Log.e("TASK", "Started task in Main thread with result from Background: " + it)
})
Log.e("MAIN", "MAIN THREAD SHOULD NOT BE BLOCKED - 1")
And the log:
2019-03-27 17:11:00.719 17082-17082/com.test.testapp E/MAIN: MAIN THREAD SHOULD NOT BE BLOCKED
2019-03-27 17:11:00.722 17082-17082/com.test.testapp E/MAIN: MAIN THREAD SHOULD NOT BE BLOCKED - 1
2019-03-27 17:11:00.722 17082-17124/com.test.testapp E/TASK: Started background task
2019-03-27 17:11:05.737 17082-17124/com.test.testapp E/TASK: Finished background task with result: The value from background
2019-03-27 17:11:05.738 17082-17082/com.test.testapp E/TASK: Started task in Main thread with result from Background: The value from background
So the question is what professional Android developers think about this solution. What problem can I get in case I'll use it. And maybe there is some reason not to use this solution.
If you're using Kotlin, the correct way to do this is via Coroutines, which would allow you to write code such as:
// Launch a coroutine that by default goes to the main thread
GlobalScope.launch(Dispatchers.Main) {
// Switch to a background (IO) thread
val retval = withContext(Dispatchers.IO) {
Log.e("TASK", "Started background task")
val retval = "The value from background"
Thread.sleep(5000)
Log.e("TASK", "Finished background task with result: " + retval)
retval
}
// Now you're back the main thread
Log.e("TASK", "Started task in Main thread with result from Background: " + retval)
}
Note that Kotlin coroutines operate under structured concurrency, so you'd generally want to avoid using GlobalScope
and instead scope your coroutine to be tied to your Activity / Fragment lifecycle. This generally needs to be done yourself right now.