kotlin-coroutines-flows — Claude Code skill: install
This skill covers Kotlin Coroutines and Flow patterns for Android and KMP. Use it when handling cancellation, concurrent work, reactive state, or tests.
npx skills add affaan-m/ECC --skill kotlin-coroutines-flows -a claude-codeWhat kotlin-coroutines-flows does
The skill guides async Kotlin code built with Coroutines, Flow, StateFlow, and SharedFlow. It covers structured concurrency, scope hierarchy, parallel work with coroutineScope and async, and sibling failure handling with supervisorScope. Examples use viewModelScope and LaunchedEffect for lifecycle-aware work instead of GlobalScope.
The material includes debounce, distinctUntilChanged, flatMapLatest, catch, and retryWhen, plus stateIn for UI state and SharedFlow for one-time effects. It also explains dispatcher choices. In KMP, Dispatchers.IO is limited to JVM/Android, so other platforms need Dispatchers.Default or a dispatcher supplied through DI.
Testing examples use runTest, Turbine, TestDispatcher, advanceUntilIdle, and fake Flows. Install it when an Android or KMP project needs a second look at coroutine structure, cancellation, Flow composition, or tests. It is unnecessary for synchronous Kotlin code.
What it helps with
- choose lifecycle-aware coroutine scopes
- load several repositories in parallel
- combine multiple Flows into UI state
- debounce search and retry failed requests
- test StateFlow updates with Turbine
Example prompts for the agent
- Review this `ViewModel`: replace `GlobalScope` with a lifecycle-aware scope and explain cancellation.
- Implement parallel loading with `coroutineScope` and `async`, while keeping sibling work alive after one failure.
- Write a `StateFlow` test with Turbine and `runTest`, including `advanceUntilIdle`.
How to install kotlin-coroutines-flows
1Skills CLI (Vercel)
npx skills add affaan-m/ECC --skill kotlin-coroutines-flows -a claude-code2Manually: put the files in place
~/.claude/skills/kotlin-coroutines-flows/SKILL.mdCopy the whole skill folder; for one project use .claude/skills/kotlin-coroutines-flows/
1Skills CLI (Vercel)
npx skills add affaan-m/ECC --skill kotlin-coroutines-flows -a codex2Manually: put the files in place
.agents/skills/kotlin-coroutines-flows/SKILL.mdFor all projects use ~/.agents/skills/
1Skills CLI (Vercel)
npx skills add affaan-m/ECC --skill kotlin-coroutines-flows -a cursor2Manually: put the files in place
.cursor/skills/kotlin-coroutines-flows/SKILL.mdCursor also reads .agents/skills/ and .claude/skills/
1GitHub CLI
gh skill install affaan-m/ECC kotlin-coroutines-flows2Manually: put the files in place
.github/skills/kotlin-coroutines-flows/SKILL.md1Skills CLI (Vercel)
npx skills add affaan-m/ECC --skill kotlin-coroutines-flows -a gemini-cli2Platform CLI
gemini skills install https://github.com/affaan-m/ECC --path skills/kotlin-coroutines-flowsManually: put the files in place
.devin/skills/kotlin-coroutines-flows/SKILL.mdThe legacy .windsurf/skills/ path still works
Skills CLI (Vercel)
npx skills add affaan-m/ECC --skill kotlin-coroutines-flows -a opencodeClaude.ai
Zip the folder and upload it in Settings → Capabilities → Skills
From the same set146
The whole set →Similar
Questions about kotlin-coroutines-flows
How do I install kotlin-coroutines-flows in Claude Code?
Run npx skills add affaan-m/ECC --skill kotlin-coroutines-flows -a claude-code. The skill file is placed at ~/.claude/skills/kotlin-coroutines-flows/SKILL.md.
What does kotlin-coroutines-flows do?
It provides patterns for Kotlin Coroutines and Flow in Android and KMP projects, covering structured concurrency, Flow operators, UI state, errors, cancellation, and testing.
Does the skill support Kotlin Multiplatform?
Yes. For KMP it uses Dispatchers.Default and Dispatchers.Main; Dispatchers.IO is treated as JVM/Android-only.
Source text
SKILL.md · MIT
---
name: kotlin-coroutines-flows
description: Kotlin Coroutines and Flow patterns for Android and KMP — structured concurrency, Flow operators, StateFlow, error handling, and testing. Use when writing coroutines or Flow code on Android or KMP, or debugging cancellation and concurrency.
metadata:
origin: ECC
---
# Kotlin Coroutines & Flows
Patterns for structured concurrency, Flow-based reactive streams, and coroutine testing in Android and Kotlin Multiplatform projects.
## When to Activate
- Writing async code with Kotlin coroutines
- Using Flow, StateFlow, or SharedFlow for reactive data
- Handling concurrent operations (parallel loading, debounce, retry)
- Testing coroutines and Flows
- Managing coroutine scopes and cancellation
## Structured Concurrency
### Scope Hierarchy
```
Application
└── viewModelScope (ViewModel)
└── coroutineScope { } (structured child)
├── async { } (concurrent task)
└── async { } (concurrent task)
```
Always use structured concurrency — never `GlobalScope`:
```kotlin
// BAD
GlobalScope.launch { fetchData() }
// GOOD — scoped to ViewModel lifecycle
viewModelScope.launch { fetchData() }
// GOOD — scoped to composable lifecycle
LaunchedEffect(key) { fetchData() }
```
### Parallel Decomposition
Use `coroutineScope` + `async` for parallel work:
```kotlin
suspend fun loadDashboard(): Dashboard = coroutineScope {
val items = async { itemRepository.getRecent() }
val stats = async { statsRepository.getToday() }
val profile = async { userRepository.getCurrent() }
Dashboard(
items = items.await(),
stats = stats.await(),
profile = profile.await()
)
}
```
### SupervisorScope
Use `supervisorScope` when child failures should not cancel siblings:
```kotlin
suspend fun syncAll() = supervisorScope {
launch { syncItems() } // failure here won't cancel syncStats
launch { syncStats() }
launch { syncSettings() }
}
```
## Flow Patterns
### Cold Flow — One-Shot to Stream Conversion
```kotlin
fun observeItems(): Flow<List<Item>> = flow {
// Re-emits whenever the database changes
itemDao.observeAll()
.map { entities -> entities.map { it.toDomain() } }
.collect { emit(it) }
}
```
### StateFlow for UI State
```kotlin
class DashboardViewModel(
observeProgress: ObserveUserProgressUseCase
) : ViewModel() {
val progress: StateFlow<UserProgress> = observeProgress()
.stateIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(5_000),
initialValue = UserProgress.EMPTY
)
}
```
`WhileSubscribed(5_000)` keeps the upstream active for 5 seconds after the last subscriber leaves — survives configuration changes without restarting.
### Combining Multiple Flows
```kotlin
val uiState: StateFlow<HomeState> = combine(
itemRepository.observeItems(),
settingsRepository.observeTheme(),
userRepository.observeProfile()
) { items, theme, profile ->
HomeState(items = items, theme = theme, profile = profile)
}.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5_000), HomeState())
```
### Flow Operators
```kotlin
// Debounce search input
searchQuery
.debounce(300)
.distinctUntilChanged()
.flatMapLatest { query -> repository.search(query) }
.catch { emit(emptyList()) }
.collect { results -> _state.update { it.copy(results = results) } }
// Retry with exponential backoff
fun fetchWithRetry(): Flow<Data> = flow { emit(api.fetch()) }
.retryWhen { cause, attempt ->
if (cause is IOException && attempt < 3) {
delay(1000L * (1 shl attempt.toInt()))
true
} else {
false
}
}
```
### SharedFlow for One-Time Events
```kotlin
class ItemListViewModel : ViewModel() {
private val _effects = MutableSharedFlow<Effect>()
val effects: SharedFlow<Effect> = _effects.asSharedFlow()
sealed interface Effect {
data class ShowSnackbar(val message: String) : Effect
data class NavigateTo(val route: String) : Effect
}
private fun deleteItem(id: String) {
viewModelScope.launch {
repository.delete(id)
_effects.emit(Effect.ShowSnackbar("Item deleted"))
}
}
}
// Collect in Composable
LaunchedEffect(Unit) {
viewModel.effects.collect { effect ->
when (effect) {
is Effect.ShowSnackbar -> snackbarHostState.showSnackbar(effect.message)
is Effect.NavigateTo -> navController.navigate(effect.route)
}
}
}
```
## Dispatchers
```kotlin
// CPU-intensive work
withContext(Dispatchers.Default) { parseJson(largePayload) }
// IO-bound work
withContext(Dispatchers.IO) { database.query() }
// Main thread (UI) — default in viewModelScope
withContext(Dispatchers.Main) { updateUi() }
```
In KMP, use `Dispatchers.Default` and `Dispatchers.Main` (available on all platforms). `Dispatchers.IO` is JVM/Android only — use `Dispatchers.Default` on other platforms or provide via DI.
## Cancellation
### Cooperative Cancellation
Long-running loops must check for cancellation:
```kotlin
suspend fun processItems(items: List<Item>) = coroutineScope {
for (item in items) {
ensureActive() // throws CancellationException if cancelled
process(item)
}
}
```
### Cleanup with try/finally
```kotlin
viewModelScope.launch {
try {
_state.update { it.copy(isLoading = true) }
val data = repository.fetch()
_state.update { it.copy(data = data) }
} finally {
_state.update { it.copy(isLoading = false) } // always runs, even on cancellation
}
}
```
## Testing
### Testing StateFlow with Turbine
```kotlin
@Test
fun `search updates item list`() = runTest {
val fakeRepository = FakeItemRepository().apply { emit(testItems) }
val viewModel = ItemListViewModel(GetItemsUseCase(fakeRepository))
viewModel.state.test {
assertEquals(ItemListState(), awaitItem()) // initial
viewModel.onSearch("query")
val loading = awaitItem()
assertTrue(loading.isLoading)
val loaded = awaitItem()
assertFalse(loaded.isLoading)
assertEquals(1, loaded.items.size)
}
}
```
### Testing with TestDispatcher
```kotlin
@Test
fun `parallel load completes correctly`() = runTest {
val viewModel = DashboardViewModel(
itemRepo = FakeItemRepo(),
statsRepo = FakeStatsRepo()
)
viewModel.load()
advanceUntilIdle()
val state = viewModel.state.value
assertNotNull(state.items)
assertNotNull(state.stats)
}
```
### Faking Flows
```kotlin
class FakeItemRepository : ItemRepository {
private val _items = MutableStateFlow<List<Item>>(emptyList())
override fun observeItems(): Flow<List<Item>> = _items
fun emit(items: List<Item>) { _items.value = items }
override suspend fun getItemsByCategory(category: String): Result<List<Item>> {
return Result.success(_items.value.filter { it.category == category })
}
}
```
## Anti-Patterns to Avoid
- Using `GlobalScope` — leaks coroutines, no structured cancellation
- Collecting Flows in `init {}` without a scope — use `viewModelScope.launch`
- Using `MutableStateFlow` with mutable collections — always use immutable copies: `_state.update { it.copy(list = it.list + newItem) }`
- Catching `CancellationException` — let it propagate for proper cancellation
- Using `flowOn(Dispatchers.Main)` to collect — collection dispatcher is the caller's dispatcher
- Creating `Flow` in `@Composable` without `remember` — recreates the flow every recomposition
## References
See skill: `compose-multiplatform-patterns` for UI consumption of Flows.
See skill: `android-clean-architecture` for where coroutines fit in layers.
© affaan-m · MIT · affaan-m/ECC