Hi! :slightly_smiling_face: I am seeing a lot of ...
# getting-started
d
Hi! 🙂 I am seeing a lot of developers ignoring structured concurrency by launching a new coroutine in a different scope, although they are already in a suspending function or have access to
CoroutineScope
. Real life example which I came across so many times can be a convenience method like this:
Copy code
fun launchWithLoading(action: suspend () -> Unit) {
    scope.launch {
        try {
            loading = true
            action()
        } finally {
            loading = false
        }
    }
}
Nothing prevents the developer from calling it in a suspending context like:
Copy code
suspend fun alreadySuspending() {
    launchWithLoading {
        // do stuff
    }
}
or like
Copy code
fun first() {
    launchWithLoading {
        another()
    }
}

fun another() {
    launchWithLoading {
        // calculate something
    }
}
although rarely they intend to break the structured concurrency. I wonder if there is something like Android Lint but that is purely for Kotlin? Almost everything I've found on the internet points me in the direction of Android Lint. But since this unrelated to Android, I would like to solve it without coupling the solution to Android or Android Studio, so that it can be potentially used in IntelliJ IDEA as well.
d
I don't think I'd say this is strictly breaking structured concurrency. I'd look at it like a convenience method. Once you inline the method, it looks a lot more clear why it's fine I think.
Although there should probably be some sort of semaphore around that loading boolean. Seems a bit thread unsafe.
d
I still find it unsafe since developers might do something like
Copy code
fun first() {
    launchWithLoading {
        another()
        finishedEvents.value = Unit
    }
}

fun another() {
    launchWithLoading {
        // heavy calculations
    }
}
Now,
finishedEvents
will be triggered even though
another()
did not finish its work 😞
You are right to point out the
loading
being thread unsafe. I simplified it here but could be easily solved with
AtomicInteger
counting how many coroutines are currently "loading".
d
The cost of abstraction I guess. With convenience comes ignorance and with ignorance comes carelessness.
launchWithLoading
should probably just be a suspend function there, so you're right.
d
I don't expect to make this work without additional hints to the lint/inspection/plugin since the intention can be unclear in many valid cases. I can imagine creating a new annotation, say
@RequiresNonSuspendingContext
that would work similarly to how
@Deprecated
or
@Nullable
annotations work. I am able to create an IDE plugin to visually highlight the incriminated piece of code as a warning/error, but that is still just highlighting. It would be better if it could be run as a part of Gradle build so that it could be used in CI as well.
d
Could easily be a compiler plugin I think.
e
Android Lint can be applied to non-Android projects
t
Recently posted in #feed - https://github.com/detekt/detekt/ I have no experience with it, just spotted possible relation.