Communitygithub.com

Kotlin Coroutines & Flows

Android および Kotlin Multiplatform プロジェクト向けの Kotlin Coroutines と Flow のパターン — 構造的並行処理、Flow オペレーター、StateFlow、エラーハンドリング、テスト。Android または KMP でコルーチンや Flow コードを記述する際、またはキャンセルや並行処理のデバッグ時に使用します。

Kotlin Coroutines & Flows とは?

このスキルは、Kotlin Coroutines と Flow を Android および Kotlin Multiplatform (KMP) プロジェクトで効果的に使用するための包括的なパターン集です。構造的並行処理のベストプラクティス(`GlobalScope` の回避、`viewModelScope` や `LaunchedEffect` の適切な使用、`coroutineScope` による並列分解、`supervisorScope` による子ジョブの独立した障害管理)を提供します。また、Cold Flow、StateFlow による UI 状態管理、`SharedFlow` による1回限りのイベント処理、`combine` による複数 Flow の統合、`debounce` や `retryWhen` といった Flow オペレーターの実践的な使い方をカバーします。`runTest` と Turbine ライブラリを用いたテスト手法や、`CancellationException` のキャッチ、`GlobalScope` の使用などのアンチパターンも解説しており、本番品質のコルーチンベースアプリケーションを構築する開発者にとって必須のリファレンスです。

対応~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/affaan-m/everything-claude-code/tree/main/skills/kotlin-coroutines-flows

お気に入りのAIに質問する

このエージェントスキルを事前に読み込んだ状態で新しいチャットを開きます。

ドキュメント

Kotlin Coroutines & Flows は何をしますか?

Android および Kotlin Multiplatform プロジェクトにおける、構造的並行処理、Flow ベースのリアクティブストリーム、コルーチンテストのパターン。

アクティベートすべきタイミング

  • Kotlin コルーチンで非同期コードを記述するとき
  • Flow、StateFlow、または SharedFlow をリアクティブデータに使うとき
  • 並行処理(並列読み込み、デバウンス、リトライ)を扱うとき
  • コルーチンと Flow のテストを行うとき
  • コルーチンスコープとキャンセルを管理するとき

構造的並行処理

スコープ階層

Application
  └── viewModelScope (ViewModel)
        └── coroutineScope { } (構造化された子)
              ├── async { } (並行タスク)
              └── async { } (並行タスク)

常に構造的並行処理を使用してください — GlobalScope は絶対に使わないでください:

// 悪い例
GlobalScope.launch { fetchData() }

// 良い例 — ViewModel のライフサイクルにスコープ付け
viewModelScope.launch { fetchData() }

// 良い例 — Composable のライフサイクルにスコープ付け
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 パターン

Cold 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
        }
    }

1回限りのイベントのための 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"))
        }
    }
}

// Composable 内で収集
LaunchedEffect(Unit) {
    viewModel.effects.collect { effect ->
        when (effect) {
            is Effect.ShowSnackbar -> snackbarHostState.showSnackbar(effect.message)
            is Effect.NavigateTo -> navController.navigate(effect.route)
        }
    }
}

Dispatchers

// 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) を使用して収集 — 収集のディスパッチャーは呼び出し元のディスパッチャー
  • @Composable 内で remember なしで Flow を作成 — 再コンポジションごとに Flow が再作成される

参考文献

スキル compose-multiplatform-patterns を参照:Flows の UI での消費について。 スキル android-clean-architecture を参照:コルーチンがレイヤーにどのように適合するか。

Individual skills in this repo

This repo contains 20 individual skills — each has its own dedicated page.

accessibility

Design, implement, and audit inclusive digital products using WCAG 2.2 Level AA. Use when building or auditing UI that must meet WCAG 2.2 Level AA, or when reviewing a change for keyboard, contrast, or screen-reader support.

affaan-m/claude-api

Anthropic Claude API patterns for Python and TypeScript. Covers Messages API, streaming, tool use, vision, extended thinking, batches, prompt caching, and Claude Agent SDK. Use when building applications with the Claude API or Anthropic SDKs.

affaan-m/everything-claude-code

End-to-end marketing campaign planning and execution. Covers audience research, positioning, campaign angle definition, landing page copy, email sequences, social posts, ad copy, short-form video scripts, and content calendars. Use as the orchestration layer for multi-channel product launches. Use when planning or executing a multi-channel product launch, or producing landing page, email, social, or ad copy.

affaan-m/everything-claude-code

Development conventions and patterns for everything-claude-code. JavaScript project with conventional commits.

affaan-m/everything-claude-code-conventions

Development conventions and patterns for everything-claude-code. JavaScript project with conventional commits.

affaan-m/frontend-design

Create distinctive, production-grade frontend interfaces with high design quality. Use when the user asks to build web components, pages, or applications and the visual direction matters as much as the code quality.

affaan-m/gget

gget CLI and Python workflow for quick genomic database queries, sequence lookup, BLAST-style searches, enrichment checks, and reproducible bioinformatics evidence logs.

affaan-m/literature-review

Systematic literature-review workflow for academic, biomedical, technical, and scientific topics, including search planning, source screening, synthesis, citation checks, and evidence logging.

affaan-m/motion-ui

Production-ready UI motion system for React/Next.js. Use when implementing animations, transitions, or motion patterns.

affaan-m/project-guidelines-example

Example project-specific skill template based on a real production application.

affaan-m/pubmed-database

Direct PubMed and NCBI E-utilities search workflows for biomedical literature, MeSH queries, PMID lookup, citation retrieval, and API-backed literature monitoring.

affaan-m/scholar-evaluation

Structured scholarly-work evaluation for papers, proposals, literature reviews, methods sections, evidence quality, citation support, and research-writing feedback.

affaan-m/uspto-database

USPTO patent and trademark data workflow for official record lookup, PatentSearch queries, TSDR checks, assignment data, and reproducible IP research logs.

agent-architecture-audit

Full-stack diagnostic for agent and LLM applications. Audits the 12-layer agent stack for wrapper regression, memory pollution, tool discipline failures, hidden repair loops, and rendering corruption. Produces severity-ranked findings with code-first fixes. Essential for developers building agent applications, autonomous loops, or any LLM-powered feature. Use when an agent or LLM feature misbehaves and the failing layer is unknown, or before shipping an agent stack.

agent-eval

Head-to-head comparison of coding agents (Claude Code, Aider, Codex, etc.) on custom tasks with pass rate, cost, time, and consistency metrics. Use when choosing between coding agents, or when a change to an agent setup needs measured pass rate, cost, and time rather than an impression.

agent-harness-construction

Design and optimize AI agent action spaces, tool definitions, and observation formatting for higher completion rates. Use when defining or revising an agent

agentic-engineering

Operate as an agentic engineer using eval-first execution, decomposition, and cost-aware model routing. Use when planning or executing engineering work that agents will carry out end to end.

agentic-os

Build persistent multi-agent operating systems on Claude Code. Covers kernel architecture, specialist agents, slash commands, file-based memory, scheduled automation, and state management without external databases. Use when building a persistent multi-agent system on Claude Code with its own memory, commands, and scheduling.

agent-introspection-debugging

Structured self-debugging workflow for AI agent failures using capture, diagnosis, contained recovery, and introspection reports. Use when an agent run fails and you need a reproducible diagnosis instead of a retry.

agent-payment-x402

Add x402 payment execution to AI agents with per-task budgets, spending controls, and non-custodial wallets. Supports Base through agentwallet-sdk and X Layer through OKX Payments / OKX Agent Payments Protocol. Use when an agent must pay for something itself and needs per-task budgets, spending controls, and a non-custodial wallet.

関連スキル