フル Compose に LifecycleEventObserver を持ち込むな

Composable のライフサイクルと Android(Activity/Fragment) のライフサイクルを分けて考える

フル Compose でアプリを作っていると、LifecycleEventObserver を使って画面のライフサイクルを監視したくなることがあります。

たしかに動きます。

しかし、そのコードが本当に必要としているのは Activity/Fragment のライフサイクルなのか、それとも Composable のライフサイクル なのか を一度考えてみるべきです。

この2つは似ていますが、別のものです。

 

🧑🏻‍💻 ComposableのLifecycle と Android Lifecycle は別物

Composable には、Composition に入る・再コンポーズされる・Composition から離れる、というライフサイクルがあります。

一方、Android には Activity や Fragment などの LifecycleOwner が持つライフサイクルがあります。


Composable
    │
    ├─ enters Composition
    ├─ recomposes
    └─ leaves Composition


Activity / Fragment
    │
    ├─ ON_CREATE
    ├─ ON_START
    ├─ ON_RESUME
    ├─ ON_PAUSE
    ├─ ON_STOP
    └─ ON_DESTROY

フル Compose だからといって Android ライフサイクルがなくなるわけではありません。

Composable のライフサイクルを扱うために Android ライフサイクルを使う必要はありません。

 

🧑🏻‍💻 LifecycleEventObserver がコードを複雑にする理由

例えば、画面が Composition に入ったときに何かを登録し、画面から離れたら解除したいとします。

Android ライフサイクルを使うと、こう書けます。

よくある書き方:


@Composable
fun MyScreen() {
    val lifecycleOwner = LocalLifecycleOwner.current

    DisposableEffect(lifecycleOwner) {
        val observer = LifecycleEventObserver { _, event ->
            when (event) {
                Lifecycle.Event.ON_START -> {
                    // start
                }

                Lifecycle.Event.ON_STOP -> {
                    // stop
                }

                else -> Unit
            }
        }

        lifecycleOwner.lifecycle.addObserver(observer)

        onDispose {
            lifecycleOwner.lifecycle.removeObserver(observer)
        }
    }
}

もちろん正しく動きます。

でも、ここで必要なのが単純に

「この Composable が存在している間だけ登録したい」

ということなら、Android ライフサイクルまで持ち込む必要はありません。

Composable のライフサイクルに合わせる:


@Composable
fun MyScreen() {
    DisposableEffect(Unit) {
        // start

        onDispose {
            // stop
        }
    }
}

こちらのほうが、コードから意図が直接読み取れます。

 

🧑🏻‍💻 Composable のライフサイクルなら DisposableEffect

DisposableEffect は、Composable が Compositionに 入ったときに処理を開始し、Composition から離れたときに後始末できます。

例えばリスナーの登録です。

Before:


@Composable
fun MyScreen() {
    val lifecycleOwner = LocalLifecycleOwner.current

    DisposableEffect(lifecycleOwner) {
        val observer = LifecycleEventObserver { _, event ->
            if (event == Lifecycle.Event.ON_START) {
                startListening()
            }
        }

        lifecycleOwner.lifecycle.addObserver(observer)

        onDispose {
            lifecycleOwner.lifecycle.removeObserver(observer)
            stopListening()
        }
    }
}

After:


@Composable
fun MyScreen() {
    DisposableEffect(Unit) {
        startListening()

        onDispose {
            stopListening()
        }
    }
}

「Composable が存在する間だけ必要なリソース」というだけなら、これで十分です。

 

🧑🏻‍💻 Coroutine なら LaunchedEffect

Coroutine を Composable のライフサイクルに合わせたい場合も、LifecycleObserver を使う必要はありません。

Before:


@Composable
fun MyScreen() {
    val lifecycleOwner = LocalLifecycleOwner.current

    DisposableEffect(lifecycleOwner) {
        val observer = LifecycleEventObserver { _, event ->
            if (event == Lifecycle.Event.ON_START) {
                // coroutineを開始
            }
        }

        lifecycleOwner.lifecycle.addObserver(observer)

        onDispose {
            lifecycleOwner.lifecycle.removeObserver(observer)
        }
    }
}

After:


@Composable
fun MyScreen() {
    LaunchedEffect(Unit) {
        // coroutine
    }
}

LaunchedEffect の Coroutine は、その Effect が Composition から外れると自動的にキャンセルされます。

つまり、


Composable
    │
    └── LaunchedEffect
            │
            └── Coroutine

という関係です。

 

🧑🏻‍💻 FlowならcollectAsStateWithLifecycle()

Flow を UI で表示するだけなら、LifecycleObserver を自分で書く必要はありません。

Before:


@Composable
fun MyScreen(viewModel: MyViewModel) {
    val lifecycleOwner = LocalLifecycleOwner.current

    DisposableEffect(lifecycleOwner) {
        val observer = LifecycleEventObserver { _, event ->
            if (event == Lifecycle.Event.ON_START) {
                // Flowをcollect
            }
        }

        lifecycleOwner.lifecycle.addObserver(observer)

        onDispose {
            lifecycleOwner.lifecycle.removeObserver(observer)
        }
    }
}

After:


@Composable
fun MyScreen(viewModel: MyViewModel) {
    val uiState by viewModel.uiState
        .collectAsStateWithLifecycle()

    Content(uiState)
}

Flow の Lifecycle-aware な収集は、専用の API に任せます。

自分でライフサイクルを監視して Flow を開始・停止するより、意図がはっきりします。

 

🧑🏻‍💻 Lifecycle イベントが必要なら LifecycleEventEffect

ここまでとは違い、本当に Android ライフサイクル のイベントそのものが必要なケースもあります。

例えば ON_RESUME や ON_PAUSE を明示的に扱いたい場合です。

その場合は、LifecycleObserver を自分で組み立てるより、ライフサイクルイベント用の API を使えます。

Before:


@Composable
fun MyScreen() {
    val lifecycleOwner = LocalLifecycleOwner.current

    DisposableEffect(lifecycleOwner) {
        val observer = LifecycleEventObserver { _, event ->
            if (event == Lifecycle.Event.ON_RESUME) {
                refresh()
            }
        }

        lifecycleOwner.lifecycle.addObserver(observer)

        onDispose {
            lifecycleOwner.lifecycle.removeObserver(observer)
        }
    }
}

After:


@Composable
fun MyScreen() {
    LifecycleEventEffect(Lifecycle.Event.ON_RESUME) {
        refresh()
    }
}

ここではじめて、Android ライフサイクル を使う理由が明確になります。

「Composable が存在する間」ではなく、「Android ライフサイクル の ON_RESUME が発生したとき」が必要だからです。

 

🤔 LifecycleEventObserver が必要になるケース

だからといって、LifecycleEventObserver が悪いわけではありません。

次のようなケースでは、直接使う意味があります。

・ 複数のライフサイクルイベントをまとめて監視したい
・ 独自のライフサイクル連携を実装している
・ Lifecycle-aware なライブラリやAPIと統合する
・ LifecycleEventEffect では表現しにくい処理がある

重要なのは、

LifecycleEventObserver を使うな、ではなく、必要のないところに持ち込まない

ということです。

 

🧑🏻‍💻 8. まとめ

迷ったら、まず「この処理はどの寿命に属しているのか」を考えます。

・ Composable のライフサイクル → DisposableEffect
・ Composable に紐づく Coroutine → LaunchedEffect
・ Flow を UIで収集 → collectAsStateWithLifecycle()
・ Activity/Fragment のライフサイクルイベント → LifecycleEventEffect
・ 複雑なライフサイクル連携 → LifecycleEventObserver

つまり、


何のライフサイクル・イベントを扱っている?
        │
        ├─ Composable
        │    ├─ Resource → DisposableEffect
        │    └─ Coroutine → LaunchedEffect
        │
        ├─ Flow → collectAsStateWithLifecycle()
        │
        └─ Android Lifecycle
             ├─ Simple event → LifecycleEventEffect
             └─ Complex integration → LifecycleEventObserver

フル Compose では、Android ライフサイクルが使えるからといって、すぐに LifecycleEventObserver を持ち込む必要はありません。

処理が属しているライフサイクルに、一番近い API を選ぶ。

それだけで、Compose のコードはかなりシンプルになります。


Related Categories :  AndroidAndroidStudioDevelopmemtJetpackComposeKMPKotlinKotlin Multiplatform MobileRecommendedToolsTrending