52

I need a reference to coroutine scope on my android Application. i did the following

class TodoApplication : Application() {
    private var job = Job()
    private val applicationScope = CoroutineScope(Dispatchers.Main + job)

    val tasksRepository: TasksRepository
        get() = ServiceLocator.provideTasksRepository(this, applicationScope)

}

Is this the way to do it. If so how can I cancel coroutines launched on this scope job.cancel()

Application class don't have onDestroy method as Activities

2
  • 2
    If you have an asynchronous worker whose lifecycle is truly global (they only die/end when your process dies), using GlobalScope or a similar life-long scope, is fine. Commented Apr 16, 2020 at 17:30
  • Dispatchers.Main cannot be used for background work. If you are updating that database or doing an API call, the recommendation is to use Dispatchers.IO. If it is just CPU background work, use Dispatchers.Default. Of course, you can redefine the scope with withContext() method or launch method. Commented Aug 16, 2021 at 15:37

5 Answers 5

72

NO , GlobalScope will NOT be suitable for Application instance.

As mention here in this article here:


There are multiple reasons why you shouldn’t use GlobalScope:

  • Promotes hard-coding values. It might be tempting to hardcode Dispatchers if you use GlobalScope straight-away. That’s a bad practice!

  • It makes testing very hard. As your code is going to be executed in an uncontrolled scope, you won’t be able to manage execution of work started by it.

  • You can’t have a common CoroutineContext for all coroutines built into the scope as we did with the applicationScope. Instead, you’d have to pass a common CoroutineContext to all coroutines started by GlobalScope.


So, one solution for this is to create your own scope like this: val applicationScope = CoroutineScope(SupervisorJob() + Dispatchers.Main) But better yet, as pointed out by @Raman in the comments, use the equivalent that's already available to you:

kotlinx.coroutines.MainScope()

We don’t need to cancel this scope since we want it to remain active as long as the application process is alive, so we don’t hold a reference to the SupervisorJob. We can use this scope to run coroutines that need a longer lifetime than the calling scope might offer in our app.

Sign up to request clarification or add additional context in comments.

4 Comments

This exact scope is now available in the coroutines library as function kotlinx.coroutines.MainScope()
would this do all its work on the Main thread?
@hmac yes it would work on the Main thread. You can use withContext if you want to switch to other thread pools.
The first two points here don't really make sense (1) overuse of a special case pattern is really the dev's fault. it's like saying you should never use a global ref or static function even though on rare occasions there's good reasons to do so (e.g., global logger & extension methods) (2) it doesn't make testing hard since you can inject the GlobalScope as a default value into the constructor. e.g., class MyApplication(val scope = GlobalScope()). you invoke the alternative constructor during test time. this is a pattern documented by google in their docs somewhere.
14

Based on the existing answers, I came up with this solution:

class YourApplication : Application() {

    companion object {
        var applicationScope = MainScope()
    }
    
    override fun onLowMemory() {
        super.onLowMemory()
        applicationScope.cancel("onLowMemory() called by system")
        applicationScope = MainScope()
    }
}

The onLowMemory() part is somewhat optional, but seems a good idea, since unfinished jobs could sit there for the lifetime of your application and use system resources.

We don’t need to cancel this scope in onDestroy since we want it to remain active as long as the application process is alive, as stated in the accepted answer.

Comments

13

Another solution could be to use the lifecycleScope of ProcessLifecycleOwner:

class YourApplication : Application() {
    ...
    override fun onCreate() {
        super.onCreate()
        with(ProcessLifecycleOwner.get()) {
            lifecycle.addObserver(yourProcessLifeCycleObserver)
            lifecycleScope.launch { TODO("Do some work here.") }
        }
        ...
    }
    ...
}

Check here for the necessary dependencies.

3 Comments

thank you. Can we use lifecycle.repeatOnLifecycle() { } instead of lifecycle.addObserver(yourProcessLifeCycleObserver)?
That line is there for context as you would normally use ProcessLifecycleOwner to add a lifecycle observer, but you can omit it if you don't need one. Considering this runs in the application context, I don't think repeatOnLifecycle will have any effect, the application object is always present until the process is terminated.
According to the documentation, "Lifecycle.Event.ON_DESTROY will never be dispatched". This may imply that the Lifecycle never moves to the DESTROYED state, in which case this scope is never canceled (see the LifecycleCoroutineScopeImpl source). If so, it doesn't behave any differently than MainScope(), in that it runs on Dispatchers.Main and is never canceled.
2

GlobalScope will be suitable for Application instance.The get() method of taskRepository variable will work as Provider Pattern. It shouldn't be in app instance. It can be replace with lazy method.

Comments

0

You can use onLowMemory() to release the CoroutineScope() on application class. onLowMemory() is similar to onDestroy()

val applicationScope = CoroutineScope(SupervisorJob() + Dispatchers.Main)

clear the scope

override fun onLowMemory() {
    super.onLowMemory()
    applicationScope.cancel()
}

onLowMemory method to release any caches or other unnecessary resources you may be holding on to, The system will perform a garbage collection for you after returning from this method.

3 Comments

I think you don't need to call this method. If you add a breakpoint, it doesn't stop in the method. And if the app is being killed, the memory will be released soon.
I think it's a good idea to cancel pending jobs in onLowMemory, however the applicationScope won't accept any new jobs, which means launch won't work. You probably should create a fresh scope there too: applicationScope = MainScope()
This is not true. onLowMemory and onDestroy are not the same. onLowMemory call is not guaranteed and very rare to be called in practice.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.