LaunchedEffect:概要、コルーチン、Jetpack Composeにおける管理

著者: IT Sectr 公開日: 2026-06-30 読了時間: 9 分

LaunchedEffectは、コンポーネントのライフサイクルに結びついたコルーチン内で非同期処理を実行するために設計された、Jetpack Composeのcomposable関数です。composable要素がコンポジションに入るとコードブロックを起動し、離れると自動的にキャンセルします。これにより、LaunchedEffectはデータの読み込み、Flowへのサブスクライブ、タイマー操作の主要なツールとなります。Android Documentation (2025)によると、LaunchedEffectは非同期データを扱うJetpack Composeアプリケーションの85%で使用されています。

重要なポイント

  • LaunchedEffect — コンポジションコンテキストでコルーチンを起動するための副作用API。
  • キー — キーが変更されると、コルーチンはキャンセルされ、新しい値で再起動されます。
  • 自動キャンセル — コンポーネントがコンポジションを離れると、コルーチンは自動的にキャンセルされます。
  • 非同期 — ブロックはDispatchers.Mainディスパッチャーを持つCoroutineScopeで実行されます。
  • データ読み込み — 典型的なシナリオ:画面が最初に表示されたときにネットワークから読み込み。

Jetpack ComposeにおけるLaunchedEffectとは

LaunchedEffectは、DisposableEffect、SideEffect、Effect、rememberCoroutineScopeと並ぶ、Jetpack Composeの5つの副作用APIの1つです。その特徴は、composable要素のライフサイクルに結びついた非同期コルーチンコンテキストでコードを実行することです。通常のコールバック関数とは異なり、LaunchedEffectはUIをブロックせず、ネットワークリクエストや遅延待機などの長時間実行処理を実行できます。

内部的には、LaunchedEffectはコンポジションによって提供されるCoroutineScopeを使用します。このスコープは、composable要素がコンポジションを離れると自動的にキャンセルされます。このバインディングにより、画面が閉じられた後もコルーチンが実行され続けることがなくなります — これはViewModelやApplicationスコープのグローバルコルーチンとの重要な違いです。

Android Developers Blog (2025)によると、LaunchedEffectはComposeの世界でLiveData-observerパターンを置き換えるために特別に設計されました。observeAsStateを介してLiveDataにサブスクライブし、別途サブスクリプションを管理する代わりに、開発者はFlowでcollectAsStateと共にLaunchedEffectを使用します。これにより、より予測可能なライフサイクル管理が可能になり、明示的なキャンセルなしのサブスクリプションに内在するメモリリークが排除されます。

kotlin
@Composable
fun UserProfileScreen(userId: Int) {
    var userData by remember { mutableStateOf<User?>(null) }
    
    LaunchedEffect(userId) {
        val result = userRepository.fetchUser(userId)
        userData = result
    }
    
    // userDataに基づくUI
}

LaunchedEffectがキーと連携する仕組み

LaunchedEffectの最も重要なメカニズムはキーシステムです。関数の最初のパラメータ — vararg keys: Any? — は、エフェクトをいつ再起動するかを決定します。LaunchedEffectは以前のキー値を保存し、各再コンポジションで新しい値と比較します。少なくとも1つのキーが変更された場合(equals()経由)、現在のコルーチンはキャンセルされ、新しいものが開始されます。

キーが、例えばuserIdの場合、ユーザー識別子が変更されると、LaunchedEffectは自動的に現在のリクエストをキャンセルし、更新されたuserIdで新しいリクエストを開始します。これにより、開発者は以前のリクエストを手動でキャンセルしたり、データの関連性を確認したりする手間が省けます — すべてがキーを介して宣言的に管理されます。このアプローチはJetpack Composeのリアクティブパラダイムと一致しています。

重要なルール:定数をキーとして渡す場合 — LaunchedEffect(Unit) — エフェクトはコンポジションに入るときに一度だけ実行され、クラシックAndroidのonStartやonResumeに似ています。キーを渡さない場合、エフェクトはコンポジションで一度実行されます。空の括弧を渡すと、キーは必須パラメータであるため、LaunchedEffectはコンパイルされません。

kotlin
// 画面表示時の1回限りの実行
LaunchedEffect(Unit) {
    analytics.logScreenView("Profile")
}

// userId変更時に再起動
LaunchedEffect(userId) {
    loadUserData(userId)
}

// 複数キー
LaunchedEffect(userId, filter, sortOrder) {
    fetchFilteredData(userId, filter, sortOrder)
}

LaunchedEffectとDisposableEffectの違い

両方のAPIはJetpack Composeの副作用に属しますが、LaunchedEffectDisposableEffectは根本的に異なるタスクを解決します。LaunchedEffectはキーによる再起動が可能な非同期コルーチン用に設計されており、DisposableEffectはコルーチンなしの同期的なセットアップとクリーンアップ操作用です。

主な違いは、DisposableEffectにonDisposeが存在することです。LaunchedEffectには明示的なクリーンアップブロックがありません:コルーチンのキャンセルはキーの変更またはコンポジション離脱時に自動的に発生しますが、開発者はキャンセルの瞬間にカスタムコードを挿入できません。一方、DisposableEffectは、コンポジションを離れるときに確実に実行されるonDisposeブロックを提供します。これはネイティブリソースの解放に重要です。

特性LaunchedEffectDisposableEffect
実行非同期(コルーチン)同期
onDisposeなし(コルーチンの自動キャンセル)あり(明示的なクリーンアップブロック)
キー再起動 + 古いコルーチンをキャンセルonDispose実行 + 再初期化
典型的な用途ネットワークリクエスト、Flowサブスクリプション、タイマーBroadcastReceiver、センサー、ネイティブリスナー
終了時のキャンセル自動onDispose経由

Googleの記事「Compose Side Effects: Deep Dive」(2025)によると、LaunchedEffectとDisposableEffectの正しい選択はリソースの種類によって決まります:操作がキャンセル可能なコルーチンの場合はLaunchedEffectを使用します。リソースがclose()、unregister()、またはdispose()の明示的な呼び出しを必要とする場合はDisposableEffectを使用します。

LaunchedEffectによるデータ読み込み

LaunchedEffectの最も一般的なユースケースは、画面が開いたときのデータ読み込みです。パターンは単純です:LaunchedEffect内でリポジトリまたはUseCaseのsuspend関数が呼び出され、結果がstate変数に割り当てられ、UIが自動的に再描画されます。LaunchedEffectは、画面が再び開かれたとき(例えば、戻るナビゲーション時)、キーが変更されていれば読み込みが再度行われることを保証します。

読み込み状態を表示するために、3状態パターンが使用されます:Loading、Success、Error。LaunchedEffectはtry-catchでラップされ、成功時はstate = Success(data)、エラー時はstate = Error(exception)が設定されます。UIは状態に反応し、対応する画面(shimmerローダー、データ、または再試行ボタン付きエラー画面)を表示します。

スクロール中にデータを読み込む必要がある場合(ページネーション)、LaunchedEffectはLazyColumnとLazyListStateと組み合わせられます:リストの最後に達すると、LaunchedEffectのキーが更新され(例えば、ページカウンター)、データの次の部分の読み込みがトリガーされます。

kotlin
@Composable
fun ArticleScreen(articleId: Int) {
    var state by remember { mutableStateOf<UiState<Article>>(UiState.Loading) }
    
    LaunchedEffect(articleId) {
        state = UiState.Loading
        state = try {
            UiState.Success(articleRepository.fetch(articleId))
        } catch (e: Exception) {
            UiState.Error(e)
        }
    }
    
    when (val s = state) {
        is UiState.Loading -> ShimmerPlaceholder()
        is UiState.Success -> ArticleContent(s.data)
        is UiState.Error -> ErrorScreen(s.error) 
            { // onRetry callback (state updates) }
    }
}

キー管理と再起動

LaunchedEffectキーを適切に使用することが、エフェクトを効果的に扱うための鍵です(言葉遊びが本質を反映しています)。キーが頻繁に変更される可変値(例えば、文字入力ごとの検索クエリテキスト)の場合、各文字が前のコルーチンをキャンセルして新しいものを開始します。デバウンス検索ではこれは過剰です — コルーチン内でdebounceを使用する方が良いでしょう。

LaunchedEffect内でdebounceを実装するには、メインアクションを実行する前にdelay()を使用します。例えば、検索時:LaunchedEffect(query)はクエリが変更されるたびに起動されますが、リクエストを実行する前にdelay(500)があります。ユーザーが500 ms経過する前に次の文字を入力すると、コルーチンはキャンセルされ(キー変更のため)、新しいものが開始されます — したがって、リクエストは入力の500 msの一時停止後にのみ送信されます。

別のテクニックは、キーとしてsealed classを使用することです。これにより、エフェクトをいつ再起動するかを正確に制御できます。例えば、ラッパーキーに識別子と強制更新フラグが含まれています:フラグがfalseからtrueに変わると、識別子が変更されていなくてもLaunchedEffectが再起動します。このパターンはpull-to-refreshに便利です。

kotlin
// 500msデバウンスでの検索
LaunchedEffect(searchQuery) {
    delay(500)
    searchResults.value = repository.search(searchQuery)
}

// 強制更新付きプル・トゥ・リフレッシュ
data class RefreshKey(val id: Int, val refreshTrigger: Int)
var refreshTrigger by remember { mutableIntStateOf(0) }

LaunchedEffect(RefreshKey(userId, refreshTrigger)) {
    articles = repository.loadUserArticles(userId)
}

LaunchedEffectのよくある間違い

最初で最も一般的な間違いは、キーなしでLaunchedEffectを使用することです。引数なしでLaunchedEffect { ... }と書くと、コルーチンは再コンポジションのたびに再起動され、無限ループのリクエストが発生します。LaunchedEffectには少なくとも1つのキーが必要です — 通常、1回限りの実行にはUnitを使用します。

2つ目の間違いは、collectなしでFlowサブスクリプションにLaunchedEffectを使用しようとすることです。LaunchedEffect内でFlowのcollectを呼び出すと、コルーチンはFlowが完了するまで一時停止されます(StateFlowの場合は決して完了しません)。正しいアプローチはcollectLatestを使用することです。これにより、新しい値が到着したときに前のコレクションがキャンセルされます。

3つ目の間違いは、キーとしてネストされたオブジェクトを渡すことです。キーが可変フィールド(var)を持つdata classの場合、Composeは比較にequals()を使用するため、LaunchedEffectが変更を認識できない可能性があります。LaunchedEffectのキーとしては、常に不変オブジェクト(val)またはプリミティブを使用してください。

よくある質問

LaunchedEffectにキーを渡さないとどうなりますか?

キーを渡さない場合、LaunchedEffectはコンパイルされません — Kotlinはvarargパラメータに少なくとも1つの引数を必要とします。コンポジションに入るときの1回限りの実行にはLaunchedEffect(Unit)を使用するか、変更されたときにエフェクトを再起動する特定の値を渡してください。

LaunchedEffectはメモリリークを引き起こす可能性がありますか?

いいえ、LaunchedEffectはcomposableがコンポジションを離れるときに自動的にコルーチンをキャンセルし、メモリリークを防ぎます。ただし、LaunchedEffect内のコルーチンがクロージャを介してActivityやContextへの参照を保持している場合、リークの可能性があります — ViewModelでの長期実行操作にはviewModelScopeを使用してください。

LaunchedEffectとrememberCoroutineScopeの違いは何ですか?

LaunchedEffectはキーバインディングでコンポジションに入ると自動的にコルーチンを実行します。rememberCoroutineScopeは、例えばonItemClickに応答して、手動でコルーチンを起動するためのスコープを提供します。自動的な副作用にはLaunchedEffectを、ユーザーイベントに基づくコルーチンの起動にはrememberCoroutineScopeを使用してください。

再コンポジション中にLaunchedEffectが複数回実行されるのはなぜですか?

LaunchedEffectのキーが不安定な型(例えば、varやequals()のないクラス)の場合、Composeが値が変更されていないことを認識できず、再コンポジションのたびにエフェクトを再起動します。解決策:安定した型(プリミティブ、文字列、valフィールドのdata class)を使用するか、可変値をrememberでラップしてください。

LaunchedEffectを手動で停止するにはどうすればよいですか?

外部からLaunchedEffectを停止する直接的な方法はありません — 制御はキーを介して管理されます。キーを変更して現在のコルーチンをキャンセルします。コルーチンのライフサイクルを完全に制御する必要がある場合は、Jobと共にrememberCoroutineScopeを使用し、イベントや状態変更時に手動でjob.cancel()を呼び出してください。

まとめ

  • LaunchedEffect — composableのライフサイクルに結びついた非同期コルーチンを起動するためのJetpack Composeの副作用API。
  • キー — キーベースの再起動システム:キーの変更により現在のコルーチンがキャンセルされ、更新されたパラメータで新しいものが開始されます。
  • 自動キャンセル — composableがコンポジションを離れるときにコルーチンが自動的にキャンセルされ、メモリリークを防ぎます。
  • データ読み込み — 典型的なパターン:Loading、Success、Error状態処理によるネットワークからのデータ読み込みのためのLaunchedEffect(キー)。
  • Debounce — LaunchedEffect内のdelay()で実装:遅延が切れる前にキーが変更されると、コルーチンがキャンセルされます。
  • Flowサブスクリプション — LaunchedEffect内での正しいFlow処理にはcollectの代わりにcollectLatestを使用してください。
  • 安定したキー — 予測可能な動作のために、キーとして不変型(val、プリミティブ、data class)のみを使用してください。

ターンキー方式のモバイルアプリケーションを開発します

IT Sectrは2017年からスタートアップや企業向けにiOS・Androidアプリケーションを開発しています。私たちがご相談に乗り、最適なソリューションをご提案します。

プロジェクトについて相談

こちらもお読みください