Search code examples
androidkotlinkotlinx.coroutinesanko

What is the correct way of using Anko Coroutines extensions?


So I am migrating an example app from RxJava to Kotlin/Anko Corountines and I am wondering if I am doing the best (first) approach of it:

fun getPopulationList() {
    val ref = asReference()

    async(UI) {
        try {
            ref().setCurrentState(ViewState.State.LOADING)
            val background = bg {
                repository.populationResponse().execute().body()
            }

            ref().let {
                it.response = background.await()
                it.mvpView?.onGetData(it.response)
                it.setCurrentState(ViewState.State.FINISH)
            }
        } catch (e: Exception) {
            e.printStackTrace()
            ref().mvpView?.onError(e)
        }
    }
}

I am using an MVP architecture where my Presenter base class had a CompositeSubscription and in the onDestroy's fragment or activity method simple unsubscribe and clear the CompositeSubscription object. But i am wondering if the asReference() function from Anko Coroutines does the same and there is no need to save a list of Deferred<T> and then iterate it and cancel one by one.

BTW if I add a Thread.sleep(5000) to simulate a big transaction and destroy the Fragment I can see in the logcat the HTTP response even after the fragment is not visible/destroyed while with RxJava doesn't happen, so I think I am not using properly.

UPDATE

 fun getPopulationList() {
    val ref = asReference()

    job = launch(UI) {

        try {
            ref().setCurrentState(ViewState.LOADING)
            val background = bg {
                Thread.sleep(5000) //simulate heavy IO

                if (isActive) {
                    repository.populationResponse().execute().body()
                } else {
                    return@bg null
                }
            }

            ref().let {
                it.response = background.await()
                it.mvpView?.onGetData(it.response)
                it.setCurrentState(ViewState.FINISH)
            }
        } catch (e: Exception) {
            RestHttpExceptionHandler().handle(UI, e, ref())
        }
    }
}

I am able to cancel the coroutine while calling job.cancel() in onDestroy() method but to make it work I have to check if the job is active or not and that translate into an if/else and a return or not data. Is there any better way to return something when the job was cancelled?


Solution

  • As you can see in asReference() source it is nothing other than a weak reference and invoke method to get reference that throws CancellationException when object is collected. it does not do anything to cancel operation. just is aware of collected object.

    so you need to keep reference of a Job or subtype of it to cancel operation.

    launch coroutine builder from kotlinx.coroutines returns a Job instance. here is an example:

    private lateinit var job: Job
    
    private fun startCoroutines() {
        val ref = asReference()
        job = launch(UI) {
            try {
                val deferred = async(parent = coroutineContext[Job]) {
                        //do some work
                        result//return
                }
    
                ref().setData(deferred.await())
            } catch (e: Exception) {
                e.printStackTrace()
            }
        }
    
    }
    
    
    override fun onDestroy() {
        super.onDestroy()
        job.cancel()
    }
    

    Notes:

    1- when result type is not important launch can be used instead of async.

    2- to do cancellation in child coroutines you must create parent/child job hierarchies. I passed parent(launch) Job reference to child coroutine(async) to achieve this.

    3- as cancellation is cooperative cancellation implementation must be done in async(see examples here).

    3- job.cancel() is used in onDestroy cancels job and it's child async. this can be done in Presenter in MVP pattern.