mcpField
A
android-coroutines

android-coroutines

github.com/new-silvermoon開発992542 回インストール13 週間前に更新
リポジトリ

android-coroutines について

Android 開発者向けの Kotlin Coroutines スキルです。データ取得やバックグラウンド処理に役立つ、構造化並行処理、ライフサイクル安全性、エラー処理、テストの指針を示します。

主な機能

  1. 01CoroutineDispatcher の注入
  2. 02安全な Flow 収集
  3. 03viewModelScope の使用
  4. 04キャンセルと例外の処理
  5. 05callbackFlow によるコールバック変換
  6. 06runTest によるテスト

活用シーン

  1. 01API やデータベースからのデータ取得
  2. 02コールバック API の Flow 変換
  3. 03ライフサイクルに伴うタスク漏れの防止
  4. 04コルーチンと ViewModel のテスト

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/new-silvermoon/awesome-android-agent-skills --skill android-coroutines

インストール後、Claude Code で/android-coroutinesを入力して読み込まれたか確認します。

詳細

Android Coroutines エキスパートスキル

このスキルは、Android で本番品質の Kotlin Coroutines コードを記述するための、信頼できるルールとパターンを提供します。構造化された並行処理、ライフサイクルの安全性、最新のベストプラクティス(2025 年の標準)を徹底します。

責務

  • 非同期ロジック:suspend 関数、Dispatcher の管理、並列実行の実装。
  • リアクティブストリーム:Flow、StateFlow、SharedFlow、callbackFlow の実装。
  • ライフサイクルとの統合:スコープ(viewModelScope、lifecycleScope)の管理と、安全な収集(repeatOnLifecycle)。
  • エラー処理:CoroutineExceptionHandler、SupervisorJob、適切な try-catch の階層の実装。
  • キャンセル可能性:ensureActive() を使用して、長時間実行される処理を協調的にキャンセルできるようにすること。
  • テスト:TestDispatcher と runTest の設定。

適用条件

ユーザーが次のような依頼をした場合、このスキルを有効にします。

  • 「API/データベースからデータを取得して」
  • 「バックグラウンド処理を実行して」
  • スレッドやタスクに関連する「メモリリークを修正して」
  • 「リスナー/コールバックを Coroutines に変換して」
  • 「ViewModel を実装して」
  • 「UI の状態更新を処理して」

重要なルールと制約

1. Dispatcher の注入(テスト容易性)
  • クラス内で Dispatcher(例:Dispatchers.IO、Dispatchers.Default)をハードコードしてはなりません。
  • 必ずコンストラクター経由で CoroutineDispatcher を注入してください。
  • 利便性のため、コンストラクター引数ではデフォルトで Dispatchers.IO を指定し、オーバーライドできるようにしてください。
// CORRECT
class UserRepository(
    private val ioDispatcher: CoroutineDispatcher = Dispatchers.IO
) { ... }

// INCORRECT
class UserRepository {
    fun getData() = withContext(Dispatchers.IO) { ... }
}
2. メインセーフティ
  • Data レイヤーまたは Domain レイヤーで定義するすべての suspend 関数は、メインセーフでなければなりません。
  • 単発の呼び出しは suspend 関数として公開してください。
  • データの変更は Flow として公開してください。
  • 呼び出し元(ViewModel)は、UI をブロックせずに Dispatchers.Main から呼び出せる必要があります。
  • リポジトリーの実装内で withContext(dispatcher) を使用して、実行をバックグラウンドに移してください。
3. ライフサイクルを考慮した収集
  • lifecycleScope.launch または launchWhenStarted(非推奨/安全ではありません)内でフローを直接収集してはなりません。
  • Activity または Fragment でフローを収集する場合は、必ず repeatOnLifecycle(Lifecycle.State.STARTED) を使用してください。
// CORRECT
viewLifecycleOwner.lifecycleScope.launch {
    viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
        viewModel.uiState.collect { ... }
    }
}
4. ViewModel スコープの使用
  • ViewModel でコルーチンを開始する場合は viewModelScope を使用してください。
  • ViewModel から View に suspend 関数を公開しないでください。ViewModel は、View が監視する StateFlow または SharedFlow を公開してください。
5. 可変状態のカプセル化
  • MutableStateFlow または MutableSharedFlow を公開してはなりません。
  • .asStateFlow() を使用するか、上位型にキャストして、読み取り専用の StateFlow または Flow として公開してください。
6. GlobalScope の禁止
  • GlobalScope は使用してはなりません。構造化された並行処理を損ない、リークにつながります。
  • 現在のスコープを超えてタスクを存続させる必要がある場合は、注入した applicationScope(Application のライフサイクルに紐づくカスタムスコープ)を使用してください。
7. 例外処理
  • CancellationException を再スローせずに、一般的な catch (e: Exception) ブロックで捕捉してはなりません。
  • runCatching を使用する場合は、必ず CancellationException を明示的に再スローしてください。
  • CoroutineExceptionHandler はトップレベルのコルーチン(launch 内)にのみ使用してください。async や子コルーチン内では効果がありません。
8. キャンセル可能性
  • コルーチンのキャンセルは協調的です。キャンセルを確認しない限り、直ちに停止することはありません。
  • 大きなリストの処理やファイルの読み取りなど、負荷の高いループでは、キャンセルを確認するために必ず ensureActive() または yield() を呼び出してください。
  • delay() や withContext() などの標準関数は、すでにキャンセル可能です。
9. コールバックの変換
  • コールバックベースの API を Flow に変換するには callbackFlow を使用してください。
  • callbackFlow ブロックの末尾では必ず awaitClose を使用して、リスナーの登録を解除してください。

コードパターン

Flow を使用した Repository パターン
class NewsRepository(
    private val remoteDataSource: NewsRemoteDataSource,
    private val externalScope: CoroutineScope, // For app-wide events
    private val ioDispatcher: CoroutineDispatcher = Dispatchers.IO
) {
    val newsUpdates: Flow<List<News>> = flow {
        val news = remoteDataSource.fetchLatestNews()
        emit(news)
    }.flowOn(ioDispatcher) // Upstream executes on IO
}
並列実行
suspend fun loadDashboardData() = coroutineScope {
    val userDeferred = async { userRepo.getUser() }
    val feedDeferred = async { feedRepo.getFeed() }
    
    // Wait for both
    DashboardData(
        user = userDeferred.await(),
        feed = feedDeferred.await()
    )
}
runTest を使用したテスト
@Test
fun testViewModel() = runTest {
    val testDispatcher = StandardTestDispatcher(testScheduler)
    val viewModel = MyViewModel(testDispatcher)
    
    viewModel.loadData()
    advanceUntilIdle() // Process coroutines
    
    assertEquals(expectedState, viewModel.uiState.value)
}

コメント