DEV Community

RockAndNull
RockAndNull

Posted on • Originally published at rockandnull.com on

Manual CoroutineScopes: how to avoid GlobalScope

Manual CoroutineScopes: how to avoid GlobalScope

Usually, when you need to run a suspend method while developing your Android app, you just use viewModelScope out of habit. No need to think about which CoroutineScope to use since all the logic happens in the View Model most of the time.

(Note: Check this out for a quick recap of the Coroutines "vocabulary")

Well, sometimes you need to run async work outside of the View Model. For instance, it's quite common for an external SDK to require an instance of a class for performing an app-specific operation.

In these cases, a common anti-pattern is to use the GlobalScopecoroutine scope. But this API is quite easy to be misused and to create memory leaks. For instance, if you are making a network request (quite a common use-case for an app-specific async operation) and there's a network issue it will stay alive and waste resources.

The ideal approach in these cases is to manually create and manage a CoroutineScope. Remember that you are responsible for canceling the async work when it's no longer needed to avoid any memory leaks and unnecessary resources allocation.

class AppRelatedProvider @Inject constructor(
    private val repository: Repository
) : SdkInterfaceForAppRelatedProvider {

    private val scope = CoroutineScope(Dispatchers.IO) // 1.

    override fun provideSomething(callback: Callback) {
        try {
            scope.launch { // 2.
                val response = repository.fetchAppRelatedSomething()
                callback.onSuccess(response)
            }
        } catch (e: RepositoryException) {
            callback.onFailure(e)
        }
    }

    protected fun finalize() {
        scope.cancel() // 3.
    }
}

Enter fullscreen mode Exit fullscreen mode
  1. Manually create the CoroutineScope. Remember that you can explicitly set the "dispatchers" (i.e. threads) that the async work will run. In case of background/network work Dispatchers.IO should be set instead of the default main thread.
  2. Run your suspend function using the manually created CoroutineScope.
  3. Don't forget to stop any async work when it's no longer needed. In this case, the finalize() is used which is called just before the garbage collector destroys an object. Ideally, you should have a more "specific" point in which your async work is no longer needed.

Hopefully, it's a bit clearer now how to create and manage your own CoroutineScopes (and to avoid GlobalScope whenever possible).

Top comments (0)