Jetpack Compose:DisposableEffect 完全ガイド
ライフサイクルと紐づいた副作用処理を安全に行うための仕組み
Jetpack Compose の副作用 API には、
- LaunchedEffect
- SideEffect
- DisposableEffect
- rememberUpdatedState
など、さまざまな種類がある。
その中で DisposableEffect は、
「リソースの登録」や「リスナーの追加」などの 開始 と、
画面やキーが変わった時の クリーンアップ を扱うための仕組み。
つまり “登録と解除をセットで扱うエフェクト”。
実務で最も重要な効果を持つ API のひとつだ。
この記事では DisposableEffect の使い方・実例・注意点・ベストプラクティスを
ひとつずつわかりやすく解説する。
1. DisposableEffect とは?
ひとことで言うと:
Compose のライフサイクルにあわせて “登録” と “解除” を確実に実行する副作用ブロック
できること
- 画面が表示されたらリスナーを登録
- 画面が消えたらリスナーを解除
- Key が変われば旧リソースを破棄し、新しいリソースに登録
- API の購読開始/終了を安全に扱う
2. 基本構文
DisposableEffect(key1, key2, ...) {
// 登録処理
onDispose {
// クリーンアップ処理
}
}
実行タイミングは以下のとおり:
- 初回:登録処理が実行される
- key が変化:いったん
onDisposeが実行されてから再登録 - 呼び出し元 composable が破棄 →
onDisposeが呼ばれる
3. 実例で理解する DisposableEffect
3.1 BroadcastReceiver を登録する(Android)
DisposableEffect(Unit) {
val receiver = object : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
// handle broadcast
}
}
context.registerReceiver(receiver, IntentFilter("MY_ACTION"))
onDispose {
context.unregisterReceiver(receiver)
}
}
register と unregister のセットを安全に扱える典型例。
3.2 Callback/Listener の登録・解除
DisposableEffect(Unit) {
val listener = object : Player.Listener {
override fun onStateChanged(state: Int) {
// …
}
}
player.addListener(listener)
onDispose {
player.removeListener(listener)
}
}
CustomView の listener でも同じパターンで使える。
3.3 SensorManager / LocationManager の登録・解除
DisposableEffect(Unit) {
sensorManager.registerListener(sensorListener, accel, SensorManager.SENSOR_DELAY_NORMAL)
onDispose {
sensorManager.unregisterListener(sensorListener)
}
}
Compose でセンサーを扱う時は必須。
3.4 ViewModel では持たないが UI Lifecyle に紐づく処理
DisposableEffect(lifecycleOwner) {
val observer = LifecycleEventObserver { _, event ->
if (event == Lifecycle.Event.ON_RESUME) {
// resume 時のみ処理
}
}
lifecycleOwner.lifecycle.addObserver(observer)
onDispose {
lifecycleOwner.lifecycle.removeObserver(observer)
}
}
画面の範囲で生きるリソースに最適。
3-5. Key 切り替えとクリーンアップ
DisposableEffect(selectedId) {
// 新しい ID で購読
notifier.subscribe(selectedId)
onDispose {
// 古い ID の購読を解除
notifier.unsubscribe(selectedId)
}
}
Key が変わった瞬間、旧リソースが必ず片付く。
4. LaunchedEffect との違い
一番わかりやすい差分表
| 項目 | DisposableEffect | LaunchedEffect |
|---|---|---|
| 主目的 | 登録+解除(リソース管理) | suspend の副作用 |
| 実行タイミング | 初回+key変更+破棄時 | 初回+key変更時 |
| 解除処理 | ✔ onDispose が呼ばれる | ❌ 自前で書かないと不可 |
| suspend OK? | ❌ NG | ✔ OK |
DisposableEffect は ライフサイクル管理のための API
LaunchedEffect は コルーチン処理のための API
用途ははっきり分かれている。
5. SideEffect との違い
| 項目 | SideEffect | DisposableEffect |
|---|---|---|
| 実行タイミング | 毎回 recompose 後 | 初回+key変更+破棄時 |
| 目的 | 外部状態の更新 | リソースの管理 |
| クリーンアップ | ❌ できない | ✔ できる |
SideEffect は「軽い通知」
DisposableEffect は「重い登録と解除」
6. よくあるミスと注意点
❌ 6-1. suspend 関数を使おうとする
DisposableEffect {
delay(1000) // ❌ エラー
}
DisposableEffect は 同期処理専用。
❌ 6-2. Compose state を書き換える
DisposableEffect(Unit) {
count++ // ❌ 無限ループの危険
}
State 更新は再compose → 再Dispose → 再登録を引き起こし、
リソースが暴走する。
❌ 6-3. onDispose を忘れてリークする
リスナー・BroadcastReceiver・Observer などを入れたら、
必ず onDispose で解除することが前提。
7. ベストプラクティス(実務向け)
7.1 外部リソースは “必ずセットで管理”
DisposableEffect(Unit) {
register()
onDispose { unregister() }
}
7.2 Key でコントロールする
ID が変わる UI(詳細画面など)では必須:
DisposableEffect(userId) {
repo.subscribe(userId)
onDispose { repo.unsubscribe(userId) }
}
7.3 カスタム View のリスナーはここで扱う
AndroidView(...) { view ->
DisposableEffect(view) {
val listener = …
view.addListener(listener)
onDispose { view.removeListener(listener) }
}
}
7.4 LifecycleObserver の登録と解除
DisposableEffect(lifecycleOwner) {
val observer = …
lifecycleOwner.lifecycle.addObserver(observer)
onDispose {
lifecycleOwner.lifecycle.removeObserver(observer)
}
}
まとめ:DisposableEffect は「開始と終了」を扱うプロのツール
- リスナー追加 → 解除
- センサー登録 → 解除
- BroadcastReceiver 登録 → 解除
- Lifecycle Observer 追加 → 解除
こういった “ライフサイクルと紐づく処理” を 絶対に安全に行いたい場面のための API。
覚えておきたいポイント:
- suspend は使えない
- onDispose は必ず書く
- Key が変わると古いリソースが破棄される
- LaunchedEffect / SideEffect と役割が全く違う
DisposableEffect を正しく使えば、
Compose の “見えないライフサイクル” に振り回されず
安定した UI と安全なリソース管理ができるようになる。