Overview

Using suspend methods with coroutines is a new norm while using Kotlin in Android, Kotlin Native or Kotlin Multiplatform. Recently, I have been searching good libraries for the multiplatform for a pet project I am working on, and I have seen many libraries and implementations that uses GlobalScope as coroutine scope. This is a huge antipattern because using GlobalScope can lead to memory leaks.

Problem with GlobalScope

Though in Android, using suspend methods is fairly easy because you can just use viewModelScope because most of the heavy lifting is being done inside viewmodels. So, No need to switch to CoroutineScope. But occasionally, there is a need for asynchronous work outside ViewModel. A common example of it would be an external SDK in your codebase may require an instance of a class performing module specific or app related tasks.

With these cases, It is a common antipattern which I have observed to use GlobalScope. This API from Kotlin can easily be misused and create memory leaks for the application process. Even Roman Elizarov advices not to use GlobalScope without a specific requirement in this blog post.

For an instance, let’s assume that you are making a network call, which is a fairly common use-case to use GlobalScope. But something bad happens and there is a network error, The network call will stay alive and waste resources because it was executed on global coroutine scope. Which leads to memory leaks.

Ideal solution

What is a solution to this then you may ask, I would suggest creating local CoroutineScope. But keep in mind, You are still responsible to cancel the asynchronous work whenever it is not needed to avoid unnecessary memory leaks and resource allocation.

  • Manually create a coroutine scope. i.e.
private val scope = CoroutineScope(Dispatchers.IO)

We must specify the Dispatcher for coroutine here, or it will run on the default thread, which can be Main Thread.

  • Run the suspended functions using the coroutine scope. i.e.
scope.launch{ 
    suspendedAsyncFunction()
    }
  • Stop any suspended asynchronous work when it is no longer needed. You can override finalize() (Java interop) but it does not always guarantee that it will be called before GarbageCollector collects resources. So just to be safe, You may want to have callbacks with high-order functions or an interface that would be attached to the lifecycle. This will make sure the cancellation of coroutine jobs whenever they are no longer required.

Wrapping up

This solution is not perfect because you will end up with multiple interface methods or high-order callbacks which would essentially pollute the codebase, but this is what I have used for my personal project to avoid any possible leaks. You can always reach me out if you have a better solution, I am looking for a better approach to this issue as well.

But the rule of thumb here is, You should ideally always avoid using GlobalScope unless there is an exceptional requirement and rely on manually created CoroutineScopes to avoid leaks. Head over to the blog post which Roman writes, He explains usage of GlobalScope in detail where it should be used and where it should not be used with nice examples.

That’s it for this one, See you in next post.