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 uselateinit
instead of aJob?
with default value ofnull
. You're exposing yourself to NPE bugs for no benefit.Using
CoroutineScope
instead ofviewModelScope
. 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. Yourconnect
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 aJob
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
}