Provides expert guidance on Kotlin Coroutines and structured concurrency for writing, reviewing, and testing safe async code with scopes, dispatchers, Flows, cancellation, and Android lifecycle awareness.
npx claudepluginhub santimattius/structured-coroutines --plugin kotlin-coroutines-skillThis skill uses the workspace's default tool permissions.
This skill provides expert guidance on Kotlin Coroutines, covering structured concurrency, scopes,
references/ref-1-1-global-scope.mdreferences/ref-1-2-async-without-await.mdreferences/ref-1-3-breaking-structured-concurrency.mdreferences/ref-1-4-awaitall-exception-propagation.mdreferences/ref-2-1-launch-last-line-coroutine-scope.mdreferences/ref-2-2-runblocking-in-suspend.mdreferences/ref-3-1-blocking-wrong-dispatchers.mdreferences/ref-3-2-dispatchers-unconfined.mdreferences/ref-3-2-main-safe-suspend.mdreferences/ref-3-3-job-context-builders.mdreferences/ref-3-5-inject-dispatchers.mdreferences/ref-4-1-cancellation-intensive-loops.mdreferences/ref-4-2-periodic-repeating-work.mdreferences/ref-4-2-swallowing-cancellation-exception.mdreferences/ref-4-3-suspend-cleanup-noncancellable.mdreferences/ref-4-4-reusing-cancelled-scope.mdreferences/ref-4-6-withtimeout-scope-cancellation.mdreferences/ref-4-7-withtimeout-resource-cleanup.mdreferences/ref-5-1-supervisor-job-single-builder.mdreferences/ref-5-2-cancellation-exception-domain-errors.mdApplies Acme Corporation brand guidelines including colors, fonts, layouts, and messaging to generated PowerPoint, Excel, and PDF documents.
Builds DCF models with sensitivity analysis, Monte Carlo simulations, and scenario planning for investment valuation and risk assessment.
Calculates profitability (ROE, margins), liquidity (current ratio), leverage, efficiency, and valuation (P/E, EV/EBITDA) ratios from financial statements in CSV, JSON, text, or Excel for investment analysis.
This skill provides expert guidance on Kotlin Coroutines, covering structured concurrency, scopes,
Dispatchers (including main-safe suspend and dispatcher injection), cancellation (including
withTimeout semantics), exception handling (CoroutineExceptionHandler, launch vs async),
Channels, Flow (cold vs hot, collectLatest, SharedFlow configuration, blocking in flow {}),
lifecycle-aware collection on Android, and testing (virtual time, setMain/resetMain). Use this
skill to help developers write safe, maintainable concurrent code aligned with Kotlin 1.9+/2.0+
conventions and official best practices.
references/.GlobalScope in production. Use framework scopes (viewModelScope,
lifecycleScope, rememberCoroutineScope), injected scopes, or local scopes (
coroutineScope { }, withContext { }). If an external scope is required, justify and document
it.async only when a return value is needed; if await() is never called, use launch.
Preserve structured concurrency: inside suspend functions use coroutineScope { } + async/
launch; do not launch in an external scope from suspend unless work must outlive the flow, and
then document it.runBlocking inside suspend functions or coroutine-based code. Avoid ending a suspend
function with coroutineScope { launch { } } as the last line when the intent is fire-and-forget
— coroutineScope waits for all children and blocks the caller; use an explicit external scope
and document it if the work must truly run in the background beyond the caller's lifetime.Dispatchers.Default for CPU-bound work, Dispatchers.Main/
Main.immediate for UI, withContext(Dispatchers.IO) for blocking I/O. Never perform blocking
I/O on Default or Main. Do not use Dispatchers.Unconfined in production unless for a rare,
documented case. Make suspend functions main-safe: move blocking work into
withContext(Dispatchers.IO) so callers on Main are never blocked. Inject CoroutineDispatcher
as a constructor parameter (default to real dispatcher; replace with TestDispatcher in tests).Job() or SupervisorJob() directly to builders (e.g. launch(Job()) { }). Use
supervisorScope { } or a scope defined with SupervisorJob() for supervisor semantics. When
running independent tasks with awaitAll, use supervisorScope instead of coroutineScope so
one failure does not cancel sibling deferreds.CancellationException; rethrow it in catch blocks.CancellationException for domain errors; use normal exceptions instead.yield(), ensureActive(), or while (isActive)
with delay(interval) so the coroutine responds to cancellation.finally, use withContext(NonCancellable) { }.scope.cancel(); use coroutineContext.job.cancelChildren() to
stop only children while keeping the scope alive.withTimeoutOrNull over withTimeout to avoid unintentionally cancelling the parent
scope. If using withTimeout, catch TimeoutCancellationException explicitly. Always ensure
resources opened inside withTimeout are cleaned up in finally.launch propagate to CoroutineExceptionHandler; in async, the
exception is stored in the Deferred and only thrown on await(). Always call await() on
async blocks to avoid silently losing exceptions.CoroutineExceptionHandler at scope level for launch uncaught exceptions.kotlinx-coroutines-test: runTest, virtual time, advanceTimeBy,
advanceUntilIdle, and inject TestDispatcher/StandardTestDispatcher; avoid real delay()
with runBlocking. Replace Dispatchers.Main using Dispatchers.setMain(TestDispatcher()) in
@Before and Dispatchers.resetMain() in @After.produce { } for channels so they close when the coroutine ends. Do not share
consumeEach across multiple consumers; use for (x in channel) per consumer.flow { } builder non-blocking; use flowOn(Dispatchers.IO) or suspend APIs.StateFlow for shared UI state (replays last value); use SharedFlow for events with
explicit replay, extraBufferCapacity, and onBufferOverflow configuration.collectLatest only when cancelling in-progress work is intentional (e.g. search); use
collect when each item must be processed to completion.repeatOnLifecycle(Lifecycle.State.STARTED) or
flowWithLifecycle to stop collection when the UI goes to background.When analyzing Kotlin projects for coroutine issues:
Read on build.gradle.kts / build.gradle for Kotlin version, kotlinx-coroutines-*
dependencies, and kotlinx-coroutines-test.Grep for CoroutineScope, GlobalScope, runBlocking, Dispatchers,
viewModelScope, lifecycleScope to locate usage patterns.| Topic / Error / Question | Reference file |
|---|---|
| GlobalScope, scope lifetime, "where should I launch?" | ref-1-1-global-scope.md |
| async without await, fire-and-forget with async | ref-1-2-async-without-await.md |
| Breaking structured concurrency, launching in external scope from suspend | ref-1-3-breaking-structured-concurrency.md |
| awaitAll exception propagation, coroutineScope vs supervisorScope for parallel tasks | ref-1-4-awaitall-exception-propagation.md |
| coroutineScope { launch { } } as last line, "wait vs don't wait" | ref-2-1-launch-last-line-coroutine-scope.md |
| runBlocking inside suspend, blocking in coroutines | ref-2-2-runblocking-in-suspend.md |
| Blocking I/O on Default/Main, wrong Dispatchers for I/O | ref-3-1-blocking-wrong-dispatchers.md |
| Main-safe suspend functions, suspend blocking caller thread, ANR | ref-3-2-main-safe-suspend.md |
| Dispatchers.Unconfined in production | ref-3-2-dispatchers-unconfined.md |
| Job() / SupervisorJob() passed to launch/async/withContext | ref-3-3-job-context-builders.md |
| Injecting Dispatchers, hardcoded dispatcher, flaky tests, testability | ref-3-5-inject-dispatchers.md |
| Cancellation in loops, long loops not responding to cancel | ref-4-1-cancellation-intensive-loops.md |
| Periodic / repeating work, polling, zombie coroutine, infinite loop without isActive | ref-4-2-periodic-repeating-work.md |
| Swallowing CancellationException, catch Exception and cancel | ref-4-2-swallowing-cancellation-exception.md |
| Suspend in finally, cleanup that needs to suspend | ref-4-3-suspend-cleanup-noncancellable.md |
| Reusing scope after cancel(), cancelChildren vs cancel | ref-4-4-reusing-cancelled-scope.md |
| withTimeout scope cancellation, TimeoutCancellationException, withTimeoutOrNull | ref-4-6-withtimeout-scope-cancellation.md |
| withTimeout resource cleanup, resource leak on timeout, finally, NonCancellable | ref-4-7-withtimeout-resource-cleanup.md |
| SupervisorJob() in a single builder | ref-5-1-supervisor-job-single-builder.md |
| CancellationException for domain errors (e.g. UserNotFound) | ref-5-2-cancellation-exception-domain-errors.md |
| CoroutineExceptionHandler vs async, exceptions in async stored in Deferred until await() | ref-5-3-exception-handler-async.md |
| Slow tests, real delay() in tests | ref-6-1-slow-tests-real-delays.md |
| Uncontrolled fire-and-forget in tests, can't wait in tests | ref-6-2-uncontrolled-fire-and-forget-tests.md |
| Dispatchers.Main in tests, setMain/resetMain, CI flaky tests | ref-6-3-setmain-resetmain.md |
| Channel not closed, manual Channel without close() | ref-7-1-channel-close.md |
| consumeEach with multiple consumers | ref-7-2-consume-each-multiple-consumers.md |
| Architecture, layers (Data/Domain/Presentation), suspend vs callbacks | ref-8-architecture-patterns.md |
| Lifecycle-aware Flow collection (Android), repeatOnLifecycle, flowWithLifecycle | ref-8-2-lifecycle-aware-flow.md |
| Blocking in flow { }, Thread.sleep in flow, flowOn | ref-9-1-flow-blocking-call.md |
| Cold vs hot flows, StateFlow, SharedFlow, shareIn, stateIn, collect | ref-9-2-cold-vs-hot-flows.md |
| collectLatest semantics, cancels previous block, search vs complete work | ref-9-3-collect-latest.md |
| SharedFlow configuration, replay, extraBufferCapacity, onBufferOverflow, backpressure | ref-9-4-shared-flow-configuration.md |
launch – Fire-and-forget or UI-triggered work that does not return a value
// Use for: Work that does not need a result; lifecycle-bound to scope
viewModelScope.launch {
updateUI(loadData())
}
async / await – Parallel work when you need a return value
// Use for: Deferred result; always await (or awaitAll) to preserve structure
coroutineScope {
val a = async { fetchA() }
val b = async { fetchB() }
combine(a.await(), b.await())
}
coroutineScope – Structured child work inside a suspend function
// Use for: Subtasks that must complete or cancel with the current scope
suspend fun loadAll() = coroutineScope {
val one = async { loadOne() }
val two = async { loadTwo() }
Pair(one.await(), two.await())
}
withContext – Switch dispatcher or run cleanup (e.g. NonCancellable)
// Use for: Blocking I/O off Main/Default; cleanup in finally
withContext(Dispatchers.IO) { readFile(path) }
withContext(NonCancellable) { db.close() }
supervisorScope – Children do not cancel each other on failure
// Use for: Independent child jobs (e.g. multiple UI updates)
supervisorScope {
launch { updateA() }
launch { updateB() }
}
produce – Channel that closes when the coroutine ends
// Use for: Single producer; automatic close when scope completes
fun CoroutineScope.flowFromApi() = produce {
for (item in api.stream()) send(item)
}
Scenario: Single network request with UI update
viewModelScope.launch {
val data = withContext(Dispatchers.IO) { repo.fetch() }
updateUI(data)
}
Scenario: Multiple parallel requests
coroutineScope {
val users = async { repo.getUsers() }
val posts = async { repo.getPosts() }
show(users.await(), posts.await())
}
Scenario: Cancellation-friendly loop
for (i in list) {
yield() // or ensureActive()
process(i)
}
coroutineScope + async/launch inside suspend; avoid
launching in external scope from suspend unless documented. For independent tasks, use
supervisorScope + awaitAll so one failure does not cancel siblings.launch. Always call await() on
every Deferred; exceptions are only thrown at await(), not caught by CoroutineExceptionHandler.withContext(IO) internally). Inject
dispatchers for testability.while(isActive) + delay for
repeating work; use yield/ensureActive in long loops; use withContext(NonCancellable) for
suspend cleanup in finally. Prefer withTimeoutOrNull; ensure resources are cleaned up on
timeout.runTest, TestDispatcher, advanceTimeBy; avoid real delay()
in tests. Replace Dispatchers.Main with setMain/resetMain for reliable CI tests.produce; if using Channel manually, document when close() is called;
one consumer per channel with for (x in channel).flow { } non-blocking (use flowOn); use StateFlow for state, SharedFlow
for events with explicit buffer/overflow config; use collectLatest only when cancelling
in-progress work is intentional.repeatOnLifecycle(STARTED) or flowWithLifecycle
so collection stops when UI is in background.Structure every code-review or refactor response as:
For conceptual-only questions, skip erroneous/optimized snippets and keep analysis and explanation.