Home > database >  Android Coroutine Usage in long jobs
Android Coroutine Usage in long jobs

Time:01-17

I want to ask about my implementation of Coroutine, I can't pass here code but I'll to explain it. I have independent class Downloader. It is responsible for check connection,download file and unpack file. But if user want to exit from this activity, the job should cancel.
Download.kt

lateinit var job : Job
fun getZip(interface){
   job = CoroutineScope(Dispatcher.IO).launch{
       val connection = async { connect() }.await()
       if(connection == ok){
           launch {
               createFolders()
               downloadZip(this).await()  //pass scope here, I have  to put await because unpack throw null on  downloaded file
               unpackZip()
            }
        }
   }
}
    private suspend fun connect(): HttpsURLConnection
    =  withContext(Dispatchers.IO) {
         //code
    }

fun downloadZip(scope: CoroutineScope ) : Deferred<Unit>
    = scope.async {
            while(lenl != -1){
                ensureActive()
                //code
            }
      }

viewModel.kt

fun stopJob(){
    downloader.job.cancel()
}

Is this quality implementation of coroutine? I very dont like downloadZip(this).await() in launch section but it's work at this moment.

I implement whole coroutine but dont know it's good way

//Edit #1 What u think guys about this?

if(connection == ok){
           launch(this.coroutineContext) {
               createFolders()
               downloadZip(this)
            }.join()

            launch(this.coroutineContext)  {
               unpackZip()
            }.join
        }

and in unzip function I delete return and change scope.async to scope.launch

CodePudding user response:

Things you're doing wrong:

  • Using lateinit for your Job. There's no reason to use lateinit instead of a Job? with default value of null. You're exposing yourself to NPE bugs for no benefit.

  • Using CoroutineScope instead of viewModelScope. This prevents the job from being automatically cancelled when the user leaves the Activity. You mentioned you want that behavior. If you actually did want the job to continue after the Activity is closed, it would be more appropriate to use a WorkManager or Service to ensure the work actually finishes.

  • Using async { ... }.await(). There is no reason to ever do this, and the compiler gives you a warning if you do. If you immediately await the result of another coroutine, then you didn't need to launch it in the first place because you wanted the result synchronously instead of asynchronously. withContext(Dispatchers.IO) { ... } should be used instead of async/await, but only if you're calling a blocking function. Your connect function is a suspend function, not a blocking function, so you don't need to to do that either.

  • Likewise, launch { ... }.join() as shown in you extra sample is equally nonsensical.

  • Launching an inner child coroutine for no reason. This is an extra layer of convolution that will make it harder to keep your function cancellable.

  • Launching a new coroutine in downloadZip(). Since you're launching a new coroutine with no relation to the one you launched it from, it is not manually cancellable unless it is the coroutine that you store in a Job property that you can directly cancel.

I would make the getZip function and downloadZip functions into suspend functions. Then the ViewModel can store it's own Job instance and cancel it directly. Since the job can be launched in the viewModelScope you get automatic cancellation behavior when the user leaves the Activity.

Example:

class Download {

    suspend fun getZip(interface){
        val connection = connect()
        if(connection == ok){
            createFolders()
            downloadZip()
            unpackZip()
         } else {
             // Probably should return some result for failure so
             // view model can decide what to do, such as showing error in UI.
         }
    }

    private suspend fun connect(): HttpsURLConnection
    =  withContext(Dispatchers.IO) {
         //code
    }

    fun downloadZip() {
        while(lenl != -1){
            yield()
            //code
        }
    }
}
// In ViewModel:

private var downloadJob: Job? = null

fun startDownload() {
    downloadJob ?: return // ignore request, already started download
    downloadJob = viewModelScope.launch {
        downloader.getZip(xxxxx)
    }
}

fun cacnelDownload() {
    downloadJob?.cancel()
    downloadJob = null
}
  • Related