I'm trying to update some library code to coroutin...
# coroutines
z
I'm trying to update some library code to coroutines 0.26 and I am noticing some boilerplate patterns. If I'm not mistaken, we want every class that launches its own coroutines to be a
CoroutineScope
, unless the coroutines created by that class should always act globally for the full lifetime of the application. This means every class that launches coroutines should be created through an extension function on
CoroutineScope
like
CoroutineScope.MyClass()
. So
MyClass
needs a constructor that takes scope as a parameter, then we need to make an extension function for
CoroutineScope
that is an exact copy of the constructor for
MyClass
except it uses
this
from
CoroutineScope
instead of taking scope as a parameter. If there is a class with several constructors that each have several parameters this can be really annoying and a bit hard to maintain. Is there a better way for me to be scoping the coroutines launched by classes?
Here is a simplified example:
Copy code
class MyClass(scope: CoroutineScope): CoroutineScope {
    private val job: Job = Job(scope.coroutineContext[Job])
    
    override val coroutineContext: CoroutineContext = scope.coroutineContext + job
    
    init {
        doStuffWhileIExist()
    }
    
    private fun doStuffWhileIExist() {
        launch { 
            while (isActive) {
                println("I am doing stuff!")
                delay(100)
            }
        }
    }
    
    fun cancel() = job.cancel()
}

fun CoroutineScope.MyClass() = MyClass(this)
p
class MyClass(scope: CoroutineScope): CoroutineScope by scope
??
z
Might work in some cases, although sometimes I think you still need to create a child job for only the coroutines in that class. But it doesn't address having to repeat the constructor as an extension function of CoroutineScope which is my main issue
d
How would you do it otherwise?
z
I don't know, I guess I am asking if I am missing something or if what I'm presenting is the idiomatic way with 0.26. For a language feature maybe you could have an extension constructor, so a constructor that could only be called with a receiver. Not sure what the design implications of that would be though.
d
I think the idea is that it should be explicit. It should be clear which scope things are running in and that they depend on one in the first place. Before 0.26.0, it was a bigger mess since you needed to pass jobs all around with much more boilerplate...
z
Yeah, I really like the change. I am just trying to figure out the best design patterns with the new design.
2
e
This pattern is indeed boiler-plate-ish, but I don’t nor how to make it less so, nor whether it is worth solving to start with. I assume that scopes in a typical app are going to be one level, so, for example, you can have a bunch of
ViewModel
classes that all implement
CoroutineScope
in the same way and thus
CoroutineScope
can be implemented by some
BaseViewModel
class, but I don’t see much use-cases for building the whole hierarchy of scope in a typical app.
z
Having a base class with most of the implementation wouldn't help with the problem of effectively duplicating the constructor in many cases though. I'm not sure if it's worth solving either, but it does feel wrong writing 2 copies of every constructor.
A way I see to fix the duplicated constructor problem would require a new language feature which is extension constructor.
CoroutineScope.constructor
. I guess on JVM it would just show up as a constructor that takes the receiver as a parameter. Again, not sure if it's worth it but maybe with this change to kotlinx.coroutines it's worth considering / reconsidering. If nothing else, it would be useful to have an IntelliJ shortcut for generating extension functions from constructors with one of the parameters as ther receiver.