
Kotlin Coroutines & Flow
FreeMaster structured concurrency in Kotlin applications.
Free · Opens the source repo
What Kotlin Coroutines & Flow does
The Kotlin Coroutines & Flow skill provides essential patterns and practices for managing asynchronous programming in Android and Kotlin Multiplatform projects. It emphasizes structured concurrency, allowing developers to write cleaner and more maintainable code by organizing coroutines within defined scopes. This skill covers the use of Flow for reactive data streams, enabling developers to handle real-time data updates efficiently. It also addresses error handling and testing strategies for coroutine-based code, ensuring robust application behavior.
With this skill, you will learn how to implement structured concurrency through various coroutine scopes, such as viewModelScope and supervisorScope. This approach prevents common pitfalls associated with the GlobalScope, fostering better lifecycle management of coroutines. You'll discover how to parallelize tasks with coroutineScope and async, making your applications more responsive and efficient.
The skill also delves into Flow patterns, including cold flows, StateFlow for UI state management, and SharedFlow for one-time events. By utilizing Flow operators, developers can implement features like debouncing search inputs and retrying failed network requests with exponential backoff. Additionally, the skill provides insights into testing coroutines and Flow, ensuring that your asynchronous code is reliable and behaves as expected.
This skill is ideal for Android developers and Kotlin Multiplatform enthusiasts who want to deepen their understanding of asynchronous programming and reactive data handling. Whether you are building complex UIs or managing background tasks, this skill equips you with the tools needed to leverage Kotlin's powerful concurrency features effectively.
When to use it
Use this skill when developing Android applications or Kotlin Multiplatform projects that require efficient handling of asynchronous tasks and reactive data streams.
When not to use it
This skill may not be suitable for projects that do not utilize Kotlin or require synchronous programming paradigms.
What you can build with it
Asynchronous Data Fetching
Implement structured concurrency to fetch data from multiple sources concurrently, improving the responsiveness of your application.
Reactive UI Updates
Utilize Flow and StateFlow to manage UI state reactively, ensuring your app responds to data changes in real-time.
Error Handling in Coroutines
Apply best practices for error handling in coroutines, allowing your application to gracefully recover from failures.
How to install Kotlin Coroutines & Flow
View source1. Install with the skills CLI
npx skills add affaan-m/ecc/kotlin-coroutines-flows --agent claude-code2. Or install it manually
Download the skill folder and drop it into ~/.claude/skills/ for all projects, or .claude/skills/ to scope it to one repo. Restart Claude Code so it picks up the new skill.
Anthropic's agentic coding CLI, and the reference implementation of Agent Skills. Drop a skill folder into ~/.claude/skills and Claude Code loads it automatically whenever a task matches the skill's description. Claude Code docs
Inside SKILL.md
Written by affaan-mKotlin コルーチン & Flow
Android および Kotlin Multiplatform プロジェクトにおける構造化並行性、Flow ベースのリアクティブストリーム、コルーチンテストのパターン。
アクティベートするタイミング
- Kotlin コルーチンで非同期コードを書く
- リアクティブデータに Flow、StateFlow、または SharedFlow を使用する
- 並行操作を処理する(並列読み込み、デバウンス、リトライ)
- コルーチンと Flow をテストする
- コルーチンスコープとキャンセルを管理する
構造化並行性
スコープ階層
Application
└── viewModelScope (ViewModel)
└── coroutineScope { } (構造化された子)
├── async { } (並行タスク)
└── async { } (並行タスク)
常に構造化並行性を使用してください — GlobalScope は絶対に使わない:
// NG
GlobalScope.launch { fetchData() }
// OK — ViewModel ライフサイクルにスコープ
viewModelScope.launch { fetchData() }
// OK — コンポーザブルライフサイクルにスコープ
LaunchedEffect(key) { fetchData() }
並列分解
並列作業には coroutineScope + async を使用:
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
子の失敗が兄弟をキャンセルしてはならない場合は supervisorScope を使用:
suspend fun syncAll() = supervisorScope {
launch { syncItems() } // ここでの失敗は syncStats をキャンセルしない
launch { syncStats() }
launch { syncSettings() }
}
Flow パターン
コールドフロー — ワンショットからストリームへの変換
fun observeItems(): Flow<List<Item>> = flow {
// データベースが変更されるたびに再エミット
itemDao.observeAll()
.map { entities -> entities.map { it.toDomain() } }
.collect { emit(it) }
}
UI 状態のための StateFlow
class DashboardViewModel(
observeProgress: ObserveUserProgressUseCase
) : ViewModel() {
val progress: StateFlow<UserProgress> = observeProgress()
.stateIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(5_000),
initialValue = UserProgress.EMPTY
)
}
WhileSubscribed(5_000) は最後のサブスクライバーが離れてから 5 秒間アップストリームをアクティブに保ちます — 設定変更を再起動なしに生き延びます。
複数の Flow の結合
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 オペレーター
// 検索入力のデバウンス
searchQuery
.debounce(300)
.distinctUntilChanged()
.flatMapLatest { query -> repository.search(query) }
.catch { emit(emptyList()) }
.collect { results -> _state.update { it.copy(results = results) } }
// 指数バックオフでリトライ
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
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"))
}
}
}
// コンポーザブルでコレクト
LaunchedEffect(Unit) {
viewModel.effects.collect { effect ->
when (effect) {
is Effect.ShowSnackbar -> snackbarHostState.showSnackbar(effect.message)
is Effect.NavigateTo -> navController.navigate(effect.route)
}
}
}
ディスパッチャー
// CPU 集約型作業
withContext(Dispatchers.Default) { parseJson(largePayload) }
// IO バウンド作業
withContext(Dispatchers.IO) { database.query() }
// メインスレッド(UI)— viewModelScope ではデフォルト
withContext(Dispatchers.Main) { updateUi() }
KMP では Dispatchers.Default と Dispatchers.Main(すべてのプラットフォームで利用可能)を使用してください。Dispatchers.IO は JVM/Android のみです — 他のプラットフォームでは Dispatchers.Default を使用するか DI で提供してください。
キャンセル
協調的キャンセル
長時間実行されるループはキャンセルを確認する必要があります:
suspend fun processItems(items: List<Item>) = coroutineScope {
for (item in items) {
ensureActive() // キャンセルされた場合は CancellationException をスロー
process(item)
}
}
try/finally でのクリーンアップ
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) } // キャンセル時でも常に実行
}
}
テスト
Turbine を使った StateFlow のテスト
@Test
fun `search updates item list`() = runTest {
val fakeRepository = FakeItemRepository().apply { emit(testItems) }
val viewModel = ItemListViewModel(GetItemsUseCase(fakeRepository))
viewModel.state.test {
assertEquals(ItemListState(), awaitItem()) // 初期値
viewModel.onSearch("query")
val loading = awaitItem()
assertTrue(loading.isLoading)
val loaded = awaitItem()
assertFalse(loaded.isLoading)
assertEquals(1, loaded.items.size)
}
}
TestDispatcher でのテスト
@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)
}
Flow のフェイク
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 })
}
}
避けるべきアンチパターン
GlobalScopeの使用 — コルーチンがリークし、構造化キャンセルがない- スコープなしで
init {}内で Flow をコレクトする —viewModelScope.launchを使用 - ミュータブルコレクションで
MutableStateFlowを使用する — 常にイミュータブルコピーを使用:_state.update { it.copy(list = it.list + newItem) } CancellationExceptionをキャッチする — 適切なキャンセルのために伝播させる- コレクトするために
flowOn(Dispatchers.Main)を使用する — コレクションディスパッチャーは呼び出し元のディスパッチャー rememberなしで@Composable内にFlowを作成する — 再コンポジションのたびにフローが再作成される
参考
スキル: compose-multiplatform-patterns で Flow の UI 消費を参照。
スキル: android-clean-architecture でレイヤーにおけるコルーチンの役割を参照。
Frequently asked questions about Kotlin Coroutines & Flow
Similar skills
Android App Development
Comprehensive guide for Android and cross-platform app development.
Add App Clip to Expo App
Integrate lightweight iOS App Clips into your Expo project.
APK Reverse
Streamline your Android APK reverse engineering process.
Swift Expert
Master iOS/macOS development with Swift and SwiftUI.
React Native Expert
Build and optimize mobile apps with React Native and Expo.
Kotlin Specialist
Master idiomatic Kotlin with expert patterns and practices.
