現在のAndroid開発ではライフサイクルメソッドのオーバーライドは推奨されてないけど、もっといい方法ないかな話

# 概要

昔は当たり前であった実装方法が現在では、推奨されなくなってたりします。たとえば、`onResume()`などのライフサイクルメソッドでもオーバーライドを現在ではしなくなっています。

[公式](https://developer.android.com/topic/architecture/recommendations?hl=ja#compose_1)には以下のように「アクティビティやフラグメントのライフサイクルメソッドをオーバーライドしない」というのを強く推奨してます。

![スクリーンショット 2023-12-17 2.59.01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/10494/192be228-54cb-66b0-4ddd-2e4244466322.png)

対応方法は書いてあるのですが、ここで、`callbackFlow`を使って、ライフサイクルも`Flow`として管理すると使いやすいかもね。という話です。

# 公式の対応方法

書いてある通りです。LifecycleObserverで対応しましょうって、Androidエンジニアだったらすぐにわかるでしょう。
もちろん、ActivityでもFragmentでもほぼ同じです。

```kotlin
class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        lifecycle.addObserver(object : DefaultLifecycleObserver {
            override fun onResume(owner: LifecycleOwner) {
                Log.d("lifecycle","AndroidView")
            }
        })
        ・・・
    }
```

コンポーザブル関数で書くとこんな感じです。

```kotlin
@Composable
fun Greeting(name: String, modifier: Modifier = Modifier) {
    val lifecycleOwner = LocalLifecycleOwner.current
    DisposableEffect(lifecycleOwner) {
        val lifecycleObserver = object : DefaultLifecycleObserver {
            override fun onResume(owner: LifecycleOwner) {
                Log.d("lifecycle","DisposableEffect")
            }
        }
        lifecycleOwner.lifecycle.addObserver(lifecycleObserver)
        onDispose {
            lifecycleOwner.lifecycle.removeObserver(lifecycleObserver)
        }
    }
```

# もうひとつの方法

上記の方法だとオーバーライドしたときと、可読性は、そんなに変わらないと思います。
で、もうひとつの方法として、人によっては昔からやってるよ的なやつだと思いますが、ライフサイクルイベントも`Flow`にして扱うという話です。
処理の内容によっては可読性は上がると思います。

```kotlin

fun Lifecycle.events(): Flow<Lifecycle.Event> = callbackFlow {
    val observer = LifecycleEventObserver { _, event -> trySend(event) }
    addObserver(observer)
    awaitClose { removeObserver(observer) }
}

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        lifecycle.events().filter { it == Lifecycle.Event.ON_RESUME }.onEach {
            Log.d("lifecycle","callbackFlow")
        }.launchIn(lifecycleScope)
    ...
```

`repeatOnLifecycle`や`collectAsStateWithLifecycle`を使えば、こんなの使うことはないと思うかもしれませんが、複数の`Flow`をマージして、それを1つで購読したい場合などでは便利かもしれません。
何を言ってるかわからないので、次にユースケースのサンプルを挙げます。

## こんなときに使えるかも

たとえば、フィードで動画プレイヤーを利用している場合です。
* ライフサイクルのSTARTでは、プレイ・イベントを送る
* ライフサイクルのSTOPでは、ストップ・イベントを送る
* `RecyclerView.OnScrollListener`も`callbackFlow`で`Flow`として管理していて、動画のアイテムが見えたときに、プレイ・イベントを送る。逆に隠れたらストップ・イベントを送る
* 動画プレイヤーのクリック・イベントも`SharedFlow`で扱い、ViewHolder内のsetOnClickListenerで、emitし、プレイやストップのイベントを送る
* それらのイベントを購読して、イベントが来たら、Playerを再生/停止をする

これも何をいってるかわからないかもなので、こんな感じです。(雰囲気で書いてるので動かない実装ですが、イメージとして見ていただけると)

以下は、ライフサイクルの実装で、スタートとストップのイベントを送っています。

```kotlin
fun Lifecycle.viewEvents(): Flow<PlayerEvent> {
    return events()
        .filter { event -> event == Lifecycle.Event.ON_START || event == Lifecycle.Event.ON_STOP }
        .map { event ->
            when (event) {
                Lifecycle.Event.ON_START -> PlayerEvent.Start
                Lifecycle.Event.ON_STOP -> PlayerEvent.Stop
                else -> {}
            }
        }
}
```

以下はRecyclerViewのスクロールリスナーで動画が表示されたときにプレイイベント、非表示になったときにストップイベントを送る実装をします。

```kotlin
fun RecyclerView.visible(): Flow<PlayerEvent>  = callbackFlow<PlayerEvent> {
    val scrollListener = object : RecyclerView.OnScrollListener() {
        override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
            val layoutManager = layoutManager
            // 色々実装しないといけないけど、こんな雰囲気。
            ・・・・
                // 動画プレイヤーが全部でたら
                trySend(PlayerEvent.Start) // TODO 本当は、トラックのindexを渡して動かす動画を特定する必要があるが割愛
            ・・・・
                // 動画プレイヤーが消えたら
                trySend(PlayerEvent.Stop) // TODO 本当は、トラックのindexを渡して止める動画を特定する必要があるが割愛
            ・・・・
        }
    }
    addOnScrollListener(scrollListener)
    awaitClose {
        removeOnScrollListener(scrollListener)
    }
}
```

以下はクリックしたときのイベントを投げる処理

```kotlin
class RecyclerViewHolder(
    private val binding: ItemBinding,
) : RecyclerView.ViewHolder(binding.root) {

    private val clicks = MutableSharedFlow<PlayerEvent>(extraBufferCapacity = 1)
    
    fun clicks() = clicks.asSharedFlow()

    init {
        // TODO 本当は動画が動いてる時だけ、Stopで、逆の場合はPlayにする必要があるが割愛
        binding.root.setOnClickListener { clicks.tryEmit(PlayerEvent.Play) }
    }
・・・
```

上の3つをひとつのフローで管理

```kotlin
val events = merge(
    lifecycle.viewEvents(),
    binding.recyclerView.visible(),
    adapter.viewEvents()
).onEach {
    when(it) {
        PlayerEvent.Play -> player.play()
        PlayerEvent.Stop -> player.stop()
    }
}.launchIn(lifecycleScope)
```

こんな感じで実装していけば、プレイヤーの処理を一箇所で管理できると思います。
上記はAndroid Viewで書いてますが、Composeでもほとんど同じです。

NewsPicksでは、最近、人気の番組をショート動画として、本編を見るきっかけになるような機能をリリースしています。
内容が面白いのでぜひ、見てほしいです。

で、実際は、これとは違う仕組みで実装しましたが、こんな感じでも良さそうという話でした。（動くコードは気が向いたら書こうと思います）