How to call suspend function from Service Android?
Asked Answered
M

4

36

How to provide scope or how to call suspend function from Service Android? Usually, activity or viewmodel provides us the scope, from where we can launch suspend but there is no similar thing in Service

Mechanism answered 14/8, 2020 at 2:28 Comment(0)
C
87

You can create your own CoroutineScope with a SupervisorJob that you can cancel in the onDestroy() method. The coroutines created with this scope will live as long as your Service is being used. Once onDestroy() of your service is called, all coroutines started with this scope will be cancelled.

class YourService : Service() {

    private val job = SupervisorJob()
    private val scope = CoroutineScope(Dispatchers.IO + job)

    ...

    fun foo() {
        scope.launch {
            // Call your suspend function
        }
    }

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

Edit: Changed Dispatchers.Main to Dispatchers.IO

Crenshaw answered 14/8, 2020 at 6:44 Comment(3)
Why would you use Dispatchers.main in a scope for a Service? Wouldn't you use Dispatchers.IO instead?Nore
Main Disoatcher is completely fine and I would argue is better solution! Service is also running on the main thread by default, so this scope keep semantics of the service. You always can wrap with IO/'Default dispatcher for any heavy operations specificallyCaroylncarp
It is best to replace job.cancel() by job.cancelChildren() at onDestroy because if your service is shown multiple time only the first coroutine will work.Lunde
E
7

for me worked like that

import androidx.lifecycle.lifecycleScope
class ServiceLife : LifecycleService() {
    private var supervisorJob = SupervisorJob(parent = null)
    override fun onCreate()  {
        super.onCreate()
        val serviceJob = lifecycleScope.launch {
            //some suspend fun
        }
          supervisorJob[serviceJob.key]
          supervisorJob.cancel()
    }
}
Ethnomusicology answered 20/4, 2022 at 8:17 Comment(1)
I think your answer is the correct one, to use LifecycleService, but you don't need serviceJob or SupervisorJob here, it looks really unnecessary for this answer, so lifecycleScope is enoughCaroylncarp
A
5

SupervisorJob() (Kotlin GitHub) is a job that provides uniderectional cancellation; it allows for cancellations to propogate downwards only.

The SupervisorJob ... is similar to a regular Job with the only exception that cancellation is propagated only downwards. [KotlinLang.org]

Use case: You have a service that makes a log entry, checks settings, and depending on those settings goes ahead and performs some actions. Do you want all children jobs of the parent job (scope) to cancel if, for instance, a job run based on settings' values throws an exception? If not (i.e. you still want your logging and check for settings jobs to complete at the least) then you want to use the SupervisorJob(), or even supervisorScope (Kotlin GitHub) for 'scoped concurrency' [KotlinLang.org], as both provide unidirectional job cancellation - and in that case the provided answer works.

Coroutine Exception Handling - Supervision (KotlinLang.org)

However, there is a more direct solution that answers the question.

To provide to your service a scope with which to run coroutines (or suspending functions) that execute blocking code, you can simply create a new CoroutineScope() with an EmptyCoroutineContext:

(Snippet from CoroutineScope Documentation)

If the given context does not contain a Job element, then a default Job() is created. This way, cancellation or failure of any child coroutine in this scope cancels all the other children, just like inside coroutineScope block [Kotlin GitHub]


class YourClass : Extended() {
    ...

    private val serviceScope: CoroutineScope( EmptyCoroutineContext )

    ...

    private inner class ServiceHandler( looper: Looper ): Handler( looper ) {
        override fun handleMessage( msg: Message ) {
            super.handleMessage( msg )

            serviceScope.launch {
               try{
+              ...
               } catch( e: Exception ) {
+              ...
               } finally {
                   stopSelf( msg.arg1 )
               }
            }
        }
    }


    override fun onCreate(){
+   ...
    }

    override fun onDestroy(){
        /* In a service, unlike in an activity, we do not
           need to make a call to the super implementation */
        //super.onDestory()
        serviceScope.cancel()
    }
}
Ario answered 13/2, 2022 at 17:5 Comment(0)
A
2

Alternatively, you can extend LifecycleService rather than Service on your class. This will provide you access to lifecycleScope in your service, just like in an Activity or Fragment.

Adequate answered 26/1 at 18:2 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.