Android kotlin concurrency
Skill adrigm06/Android-Engineering-Skill/skills/android-kotlin-concurrency
A modular, composable, production-grade Android skill system for agentic engineering workflows, designed to behave like a senior Android Staff Engineer under real-world constraints.
npx -y skills add adrigm06/Android-Engineering-Skill --skill android-kotlin-concurrencyAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 3 stars3 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
What its author says it does
Copied from the file, not written here
Kotlin coroutines and Flow engineering skill for structured concurrency, scope lifecycle, dispatcher strategy, and reactive stream design. Use when coroutine leaks, cancellation failures, dispatcher misuse, Flow vs StateFlow selection, or thread-safety in suspend functions are in scope.
SKILL.md
8.2 KB, as published. Nobody here has run it
Purpose
Design correct and leak-free Kotlin concurrency in Android using structured concurrency principles, evidence-driven dispatcher strategy, and safe reactive stream composition.
Scope and authority
This skill is lead authority for:
- coroutine scope design and lifecycle binding
- Flow / StateFlow / SharedFlow selection and composition
- dispatcher strategy and thread-safety decisions
- cancellation safety and structured concurrency enforcement
- suspend function design and cold/hot stream selection
This skill must defer when applicable:
- architectural ownership of ViewModels and repositories to
android-architecture - UI state rendering and recomposition to
android-compose - performance profiling and ANR root-cause to
android-performance
When to use
- coroutine scope is leaking or outliving its lifecycle (e.g.,
GlobalScopemisuse) - cancellation is not propagating correctly across suspend calls
FlowvsStateFlowvsSharedFlowselection is unclear or wrong- dispatcher choice causes jank or blocks the main thread
- suspend function is not cancellable or has unsafe shared mutable state
- structured concurrency violations in production or test code
When not to use
- when the primary question is ViewModel architecture or repository layering (
android-architecture) - when the primary question is jank or startup profiling without concurrency root cause (
android-performance) - when the question is Compose state management specifically (
android-compose)
Decision engine workflow
- Identify the concurrency problem class (scope, dispatcher, cancellation, stream type, thread-safety).
- Classify lifecycle binding context (ViewModel, Fragment, Service, WorkManager, standalone).
- Select stream type and scope strategy branch.
- Validate dispatcher choice and cancellation propagation.
- Run cross-skill checks (architecture for ownership, performance for hot-path impact).
- Resolve conflicts using authority model from
../../AGENTS.md. - Return implementation guidance with fallback options.
Branching decision tree
Branch A: scope and lifecycle binding
ViewModel(UI-triggered async work):- use
viewModelScope— auto-cancelled on ViewModel cleared. - do not use
GlobalScopeor manually constructed scopes that outlive the ViewModel.
- use
Fragment / Activity(lifecycle-bound observation):- use
lifecycleScopewithrepeatOnLifecyclefor Flow collection. - avoid
launchWhenStarted— it suspends but does not cancel on STOP.
- use
Repository / data layer(no lifecycle):- inject a
CoroutineScopefrom DI with explicit ownership. - never create ad-hoc
CoroutineScope(Dispatchers.IO)without a cancellation owner.
- inject a
WorkManager / Service:- use
CoroutineWorkeror bind to an injected scope with explicit job cancellation.
- use
Branch B: stream type selection
StateFlow: current-value holder for UI state; always has a value; replays last to new collectors.SharedFlow: for events/effects that must not replay (navigation events, one-time signals).Flow(cold): on-demand data streams (Room queries, network, Paging); no independent lifecycle.Channel: only when one-to-one producer-consumer delivery with backpressure is required; preferFlowotherwise.
Branch C: dispatcher strategy
Dispatchers.Main: UI updates, StateFlow emissions from ViewModel, navigation triggers.Dispatchers.Main.immediate: when already on main thread and dispatch overhead must be avoided.Dispatchers.IO: blocking I/O (network, disk, database). Not for CPU-intensive work.Dispatchers.Default: CPU-intensive computation (parsing, sorting, encoding).Dispatchers.Unconfined: avoid in production; use only in tests withUnconfinedTestDispatcher.
Do not apply withContext(Dispatchers.IO) as a blanket jank fix — profile before changing dispatcher.
Branch D: cancellation safety
- Long-running suspend loops must call
ensureActive()or checkisActiveat iteration boundaries. - Use
withTimeout/withTimeoutOrNullfor deadline-bound operations. - Avoid swallowing
CancellationExceptionintry/catch (e: Exception)blocks — always re-throw it. - Prefer
supervisorScopewhen child failure must not cancel siblings.
Branch E: testing strategy
- Use
StandardTestDispatcherwithadvanceUntilIdle()for deterministic coroutine control. - Inject dispatchers via constructor — never hardcode
Dispatchers.IOin production classes. - Use
turbinefor Flow assertion in unit tests. - Replace
delay()with virtual time in tests viaTestCoroutineScheduler.
Conflict handling and composition
When used with other skills:
- With
android-architecture:- scope ownership must align with layer ownership; a Repository must not own a ViewModel-scoped job.
- With
android-performance:- measured jank may implicate wrong dispatcher; escalate with trace evidence before changing dispatch strategy.
- With
android-testing:- flaky tests caused by real-time delays or shared coroutine state require deterministic dispatcher injection.
- With
android-compose:StateFlowconsumed in Compose must be collected withcollectAsStateWithLifecycle, notcollectAsState.
Quantitative gates
Use measurable gates and label each pass | at-risk | fail:
- scope-leak gate: no coroutines alive after owning lifecycle ends (verified by leak detection or test)
- cancellation propagation gate:
CancellationExceptionreaches structured root without suppression - dispatcher correctness gate: no blocking calls on
Dispatchers.Main; no UI updates fromDispatchers.IO - test determinism gate: all coroutine tests use injected test dispatcher; no
Thread.sleep()or realdelay()
If no observable evidence exists, return a measurement and instrumentation plan before recommending dispatcher or scope changes.
Tradeoff realism
Allow context-justified non-ideal decisions when explicit:
GlobalScopemay be acceptable for fire-and-forget analytics with no cancellation requirement — must be labeled.runBlockingis acceptable in tests and top-level entry points; never acceptable in production suspend chains.- Mixing cold Flow and SharedFlow is acceptable during migration with an explicit cutover plan.
Do not present temporary concurrency shortcuts as final architecture.
Uncertainty protocol
Always report confidence:
High(>= 0.80)Medium(0.60-0.79)Low(< 0.60)
If confidence is medium/low:
- state missing context (scope ownership, lifecycle binding, whether caller is suspend or not)
- provide at least one conservative fallback
- escalate to
android-architecturewhen scope ownership is structurally ambiguous - escalate to
android-performancewhen dispatcher change is proposed without profiling evidence
Cross-skill handoff payload
Use the standard payload defined in ../../AGENTS.md (section: Cross-skill handoff contract).
Set requesting_skill to android-kotlin-concurrency.
Output contract
Follow global order from ../../AGENTS.md:
Context and constraintsDecision and rationaleAlternatives consideredTradeoffsRisks and mitigationsConfidence and unknownsCross-skill impactsNext implementation steps
Also include:
Scope and lifecycle binding strategyStream type selectionDispatcher strategyCancellation safety analysisTesting approach
Anti-pattern detection
GlobalScopeusage without explicit fire-and-forget justificationlaunchWhenStartedfor Flow collection (suspends but does not cancel)- blocking calls (
Thread.sleep,runBlocking) inside suspend functions or coroutines - catching
CancellationExceptionwithout re-throwing - hardcoded
Dispatchers.IOin production classes without injection SharedFlowused for UI state that needs a current value (useStateFlowinstead)- missing
repeatOnLifecyclefor lifecycle-aware Flow collection in UI layer
Related resources
references/kotlin-concurrency-patterns.md