2025年2月15日土曜日

Jetpack ComposeでのLaunchedEffectとwhileループの動作について

基本的な実装と動作

以下のようなコードでは、アプリ(Activity)がpause中でもwhileループは自動的には停止しません。

LaunchedEffect(key1 = Unit) {
     while (true) {
         indicatorAlpha.floatValue = 1f
         delay(1000L)
         indicatorAlpha.floatValue = 0f
         delay(1000L)
     }
}

なぜかというと、

  • LaunchedEffectはComposable関数のライフサイクルに連動するが、Activityのライフサイクルとは直接連動しない
  • whileループ内のdelayはsuspend関数だが、コルーチン自体は終了しない
  • ActivityがonPause()に入ってもコルーチンは実行を継続する

という理由があるためです。

ライフサイクルに応じた制御の実装

Activityのpause中にループを停止するには以下のような実装が必要です。

var isRunning by remember { mutableStateOf(true) }

LaunchedEffect(isRunning) {
    while (isRunning) {
        indicatorAlpha.floatValue = 1f
        delay(1000L)
        indicatorAlpha.floatValue = 0f
        delay(1000L)
    }
}

DisposableEffect(key1 = LocalLifecycleOwner.current) {
    val observer = LifecycleEventObserver { _, event ->
        if (event == Lifecycle.Event.ON_PAUSE) {
            isRunning = false
        } else if (event == Lifecycle.Event.ON_RESUME) {
            isRunning = true
        }
    }
    LocalLifecycleOwner.current.lifecycle.addObserver(observer)
    onDispose {
        LocalLifecycleOwner.current.lifecycle.removeObserver(observer)
    }
}

Decomposeを使用したMultiplatformアプリでの実装

Decomposeを使用している場合は、Decomposeのライフサイクルを使用することができます。

class YourComponent(
    componentContext: ComponentContext
) : ComponentContext by componentContext {

    private val _isRunning = MutableValue(true)
    val isRunning: Value<Boolean> = _isRunning

    init {
        lifecycle.doOnPause { _isRunning.value = false }
        lifecycle.doOnResume { _isRunning.value = true }
    }
}

まとめ

Jetpack ComposeでLaunchedEffectとwhileループを使用する際は、ActivityやDecomposeのライフサイクルを使って適切なタイミングでループを停止・再開することができます。 これにより、アプリがバックグラウンドに移行した際に不要な処理が実行され続けることを防いでバッテリー消費を抑えることができます。 回しっぱなしのループを作らないように心がけましょう。