question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Common usage of CoroutineScope Job functions are difficult to understand

See original GitHub issue

One problem we have on our team when onboarding developers to Coroutines is the complex access to the Job in a CoroutineScope - specifically scope.coroutineContext[Job]!!. Access to methods like .invokeOnCompletion, cancel, etc are unwieldy to use because of the Job nullability and violation of the Law of Demeter.

// easy to use/understand
scope.invokeOnCompletion { }
scope.cancel {  }

// difficult to understand
scope.coroutineContext[Job]!!.invokeOnCompletion { }
scope.coroutineContext[Job]!!..cancel { }

The latter is more difficult because you have to understand that a CoroutineScope is just a wrapper around a CoroutineContext that will always have a Job (even though the API says a Job can be null). And once it is undestood, it’s a pretty verbose API.

Possible Solutions

I see two possible solutions going forward (though both could be implemented).

  1. Put commonly used functions like .cancel(), invokeOnCompletion{ }, etc as extension functions on CoroutineScope.
  2. Add an extension function scope.job that returns an non-null Job - because a Job should never be null for a CoroutineScope.

note: scope.cancel() was added in https://github.com/Kotlin/kotlinx.coroutines/pull/866

Issue Analytics

  • State:closed
  • Created 5 years ago
  • Reactions:6
  • Comments:9 (5 by maintainers)

github_iconTop GitHub Comments

6reactions
ZakTaccardicommented, Jan 15, 2019

what Job API are you using apart from invokeOnCompletion?

We are using:

job.cancel()
job.cancelAllChildren()
job.invokeOnCompletion()
job.isCompleted // rare
job.isActive // rare
job.cancelAndJoin() // rare

Could you please elaborate what Job API are you using apart from invokeOnCompletion

I’m currently using it to signal a ViewModel.onCleared() event, instead of relying on extending the android arch components ViewModel

While standalone .job extension may be useful for advanced usages, we are still not sure.

Ultimately, I think it’s important to understand that the handle on coroutines created from a CoroutineScope is a Job. Is it safe to assume that all CoroutineScope instances should have a Job associated with this?

I don’t think it’s very important (for beginners) to understand that a Job lives inside a CoroutineContext. In most common usage - the interesting bits of using a CoroutineScope are what dispatcher is used, and being able to control the scope’s lifecycle via Job. I think this is an argument in favor of a standalone .job extension.

val scope = CoroutineScope(Dispatchers.Main) // specify the dispatcher
// control lifecycle
scope.job.invokeOnCompletion { }

The above is simple to onboard beginner devs to. The following is not:

val scope = CoroutineScope(Dispatchers.Main)
scope.coroutineContext[Job]!!.invokeOnCompletion {  }

It requires a dev to understand that:

  1. A Job is a special part of a CoroutineContext
  2. Use the Job.Companion as a Key<Job> for accessing the Job instance of a CoroutineContext
  3. Job should never be null in a CoroutineScope, so it is safe to force unwrap it (!!)
  4. a Job will automatically be created for you if you don’t specify it

Not requiring a dev to understand those things at the very beginning for the very basics would be a big win

2reactions
elizarovcommented, Feb 20, 2019

I’d use this opportunity to take a better look at Job use-cases and cover the missing ones with CoroutineScope. I see two main missing pieces that require a Job here:

  • cancelChildren() – this is easy to add. The only problem with it is that, in general, this function is extremely hard to use right in a race-free manner. Can you elaborate what are use-cases for this particular function?

  • invokeOnCancellation { ... } – this is quite an advanced low-level function, because, for one thing, it does not specify which context it is invoked in, so it is easy to use incorrectly in a non-thread-safe manner. Can you give a bit more of example of how you use it, please. We might be able to design a safer alternative that would be a better fit for CoroutineScope extension.

Read more comments on GitHub >

github_iconTop Results From Across the Web

Coroutine scope functions - Kt. Academy
The first approach is calling suspending functions from a suspending function. The problem with this solution is that it is not concurrent ...
Read more >
Best practices for coroutines in Android
This page presents several best practices that have a positive impact by making your app more scalable and testable when using coroutines.
Read more >
7 common mistakes you might be making when using Kotlin ...
Common Mistake #1: Instantiating a new job instance when launching a Coroutine. Sometimes you need a job as a handle to your Coroutine...
Read more >
How to make sense of Kotlin coroutines | by Joffrey Bion
The coroutineScope builder. You may have noticed that the use of runBlocking is discouraged from inside coroutines. This is because the Kotlin ...
Read more >
Coroutines & Patterns for work that shouldn't be cancelled
On Android, you can use the CoroutineScope s provided by Jetpack: viewModelScope or lifecycleScope that cancel any running work when their ...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found