Kotlin Coroutines in Android Service

17.4k views Asked by At

I have an Android service which starts and syncs different types of data with the server when it's online. I'm new to Kotlin coroutines and I'm trying to accomplish the following:

fun syncData{
//Job1 make retrofit call to server
//Job2 make retrofit call to server after job1 is done.
//Job3 make retrofit call to server after job2 is done and so on. 
//After all jobs are done I'll stop service.
}

I'm following this post: Kotlin Coroutines the right way in Android

Which brought me to this solution:

fun syncData() = async(CommonPool){
    try{
        val sync1 = async(CommonPool){
            job1.sync()
        }

        val sync2 = async(CommonPool){
            job2.sync()
        }

        val sync3 = async(CommonPool){
            job3.sync()
        }

        val sync4 = async(CommonPool){
            job4.sync()
        }

        job1.await()
        job2.await()
        job3.await()
        job4.await()
    }catch (e: Exception){
    }finally {
        stopSelf()
    }
}

But when I get retrofit's log on logcat, every call is mixed. Calls from job3 comes before job1, and so on. How can I execute them in a pipeline? I'm kinda lost in Kotlin's coroutines so I don't know how exactly to implement this.

3

There are 3 answers

0
s1m0nw1 On

If you have to execute them one after the other and, like you say, they depend on each other, it's not a valid use case for concurrent execution like you're doing with Coroutines here. Just do it sequentially, then.

In your example, you're dispatching all tasks at once and wait for their completion. It's totally random which one will be executed first.

It would work if, for example Job1, Job2 and Job3, were independent of each other and could run in parallel. Then you'd dispatch each of them to a Coroutine and in the end wait for each to complete. You would not care about the execution order.

These ideas are independent of coroutines and are valid for every concurrent programming style.

1
Bohsen On

You could add laziness to every sync().

Just change the implementation to this:

fun syncData() = async(CommonPool){
        try{
            val sync1 = async(coroutineContext, start = CoroutineStart.LAZY){
                job1.sync()
            }

            val sync2 = async(coroutineContext, start = CoroutineStart.LAZY){
                job2.sync()
            }


            val sync3 = async(coroutineContext, start = CoroutineStart.LAZY){
                job3.sync()
            }

            val sync4 = async(coroutineContext, start = CoroutineStart.LAZY){
                job4.sync()
            }
            job1.await()
            job2.await()
            job3.await()
            job4.await()
        }catch (e: Exception){
        }finally {
            stopSelf()
        }
    }

Each job will not execute before the job before it has executed.

Take a look at the coroutines guide.

3
GV_FiQst On

Agree with @s1m0nw1 and his answer

It's a way too hard to approach. All you need is as easy as this:

val job = SupervisorJob()

fun syncData() = launch(job) {
    try { 
        val result1 = makeRetrofitCall1()
        val result2 = makeRetrofitCall2(result1)
        ...
        val resultN = makeRetrofitCallN(resultNMinusOne)
    } catch(e: Exception) {
        // handle exception
    } finally {
        stopSelf()
    }
}

override fun onDestroy() {
    super.onDestroy()
    job.cancel()
}

Here makeRetrofitCall means a direct API call as retrofitClient.getData() where retrofitClient is

interface RetrofitClient {

    @GET("endpoint")
    suspend fun getData(): MyCustomResult
}