How I got rid of all my AsyncTasks at once
Tags: Android App Development, AndroidDev, Asynctask, Kotlin Coroutines, Kotlin

I’ve chosen coroutines. But instead of rewriting all my AsyncTasks individually I’ve implemented my own SupportAsyncTask (remembering the former support library) that invisibly uses coroutine inside but works along the same lifecycle and performs the same callbacks than the original AsyncTask did.

This way it was enough to simply change the base class of my AsyncTasks. And now execute() can be called directly.

Ok, I haven’t re-implemented the full AsyncTask behaviour just the “minimal” part I actually used… and what may be enough for you as well:

execute(), cancel(), isCancelled() implementations,

asynchronous call of doInBackground() (without implementation),

publishProgress() implementation to dispatch UI update to UI/Main thread,

onProgressUpdate(), onCancelled(), onPostExecute() callbacks on UI/Main thread (with default implementations)

That’s all.

Here and now I would like to give you as simple to understand example as possible but mainly the following Kotlin code has been working fine for 3 months in one of my apps with 5K+ daily users. I hope it will be useful for someone else too. (Please feel free to make logging more detailed, add onPreExecute() callback, etc.)

import android.util.Log

import kotlinx.coroutines.*



abstract open class SupportAsyncTask<ParamType, ProgressType, ResultType> {



enum class Status {

PENDING, RUNNING, FINISHED

}



// In our app all AsyncTasks perform I/O operations

val scope = CoroutineScope(Dispatchers.IO)



var status = Status.PENDING

var canceled = false



fun execute(vararg params: ParamType?) {

if (!canceled && scope.isActive){

try {

scope.launch {

var result: ResultType? = null

try {

if (status == Status.PENDING) {

status = Status.RUNNING

result = doInBackground(*params)

status = Status.FINISHED

}

} catch (ex: CancellationException) {

Log.w(TAG, "Forced to cancel.")

} catch (ex: Exception) {

Log.e(TAG, "Unexpected error.")

} finally {

withContext(Dispatchers.Main) {

try{

if (canceled) {

onCancelled(result)

} else {

onPostExecute(result)

}

} catch (ex: Exception){

Log.e(TAG, "Unexpected error.")

}

}

}

}

} catch (ex: java.lang.Exception){

Log.e(TAG, "Cannot launch task.")

}

}

}



abstract protected fun doInBackground(vararg params: ParamType?) : ResultType



fun publishProgress(vararg values: ProgressType) {

if (!canceled && scope.isActive) {

try {

scope.launch {

withContext(Dispatchers.Main){

try {

onProgressUpdate(*values)

} catch (ex: Exception) {

Log.e(TAG, "Unexpected error.");

}

}

}

}catch (ex: java.lang.Exception){

Log.e(TAG, "Cannot publish progress.")

}

}

}



protected open fun onProgressUpdate(vararg values: ProgressType?) {

// Default implementation. Does nothing.

}



fun cancel(mayInterruptIfRunning: Boolean) : Boolean{

if (!canceled && status != Status.FINISHED) {

canceled = true

if (status == Status.RUNNING){

if (mayInterruptIfRunning) {

try {

scope.cancel()

return true

}catch (ex: IllegalStateException){

return false

}

} else {

return false

}

}

status == Status.FINISHED

return true

}

return false;

}



protected open fun onCancelled(result: ResultType?){

onCancelled();

}



protected open fun onCancelled(){

// Default implementation. Does nothing.

}



fun isCancelled() : Boolean {

return canceled;

}



protected open fun onPostExecute(result: ResultType?) {

// Default implementation. Does nothing.

} companion object {

private val TAG: String? =SupportAsyncTask::class.simpleName

} }

Of course using this code it is still your responsibility to hold and cancel your AsyncTasks instances in case of certain lifecycle events of your viewholders (Activites or Fragments). Using ViewModels makes it much easier but that is a different topic ;-)