【Jetpack Compose】@Composable のライフサイクルをシンプルに使うには DisposableEffect

なんか、いろんな方法があるんだなあ、と。

👉 Lifecycle を Compose と統合する  |  App architecture  |  Android Developers

👉 ComposeでLifecycleを監視する(2023年9月バージョン) - Kenji Abe - Medium

いろいろ試しながらどこから使っていくのがいいか。を考える。

 

🤔 参考になるコードたち

調べてみると、

DisposableEffect を中心に考えるのがいいだろう、

と思える。


@Composable
fun LifecycleEffect(
  onCreate: () -> Unit = { },
  onStart: () -> Unit = { },
  onResume: () -> Unit = { },
  onPause: () -> Unit = { },
  onStop: () -> Unit = { },
  onDestroy: () -> Unit = { },
  onAny: () -> Unit = { }
) {
  val lifecycleOwner = LocalLifecycleOwner.current
  DisposableEffect(lifecycleOwner) {
    val observer = LifecycleEventObserver { _, event ->
      when (event) {
        Event.ON_CREATE -> onCreate()
        Event.ON_START -> onStart()
        Event.ON_RESUME -> onResume()
        Event.ON_PAUSE -> onPause()
        Event.ON_STOP -> onStop()
        Event.ON_DESTROY -> onDestroy()
        Event.ON_ANY -> onAny()
      }
    }
    lifecycleOwner.lifecycle.addObserver(observer)
    onDispose {
      lifecycleOwner.lifecycle.removeObserver(observer)
    }
  }
}

👉 PatchNote-Android/app/src/main/java/com/easyhz/patchnote/core/common/util/Lifecycle.kt at e24ff1d32831b54177348e079412c95b17e3f0f4 · easyhz/PatchNote-Android


@Composable
fun LifecycleEffect(
  lifecycleOwner: LifecycleOwner = LocalLifecycleOwner.current,
  onStart: (suspend () -> Unit)? = null,
  onPause: (suspend () -> Unit)? = null,
  onStop: (suspend () -> Unit)? = null
) {
  val scope = rememberCoroutineScope()
  val currentOnStart by rememberUpdatedState(onStart)
  val currentOnPause by rememberUpdatedState(onPause)
  val currentOnStop by rememberUpdatedState(onStop)

  DisposableEffect(lifecycleOwner) {
    val observer = LifecycleEventObserver { _, event ->
      scope.launch {
        when (event) {
          Lifecycle.Event.ON_START -> currentOnStart?.invoke()
          Lifecycle.Event.ON_PAUSE -> currentOnPause?.invoke()
          Lifecycle.Event.ON_STOP -> currentOnStop?.invoke()
          else -> {}
        }
      }
    }
    lifecycleOwner.lifecycle.addObserver(observer)

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

👉 itunes/app/src/main/java/com/simgesengun/itunes/ui/launchedEffect/LifecycleEffect.kt at cc63faa4eb78f22a7e2e8b95c78e1ca30f4f11ad · simgesengun/itunes


@Immutable
enum class LifecycleEvent {
  OnStop
}

@Immutable
data class LifecycleHandler(
  val id: String,
  val event: LifecycleEvent,
  val onEvent: () -> Unit
)

@Composable
fun <R : Any> DestinationScope<R>.LifecycleEffect(
  event: LifecycleEvent,
  onEvent: () -> Unit
) {
  val id = remember { randomUUID() }
  DisposableEffect(this, id, event, onEvent) {
    navigator.update {
      it.copy(lifecycleHandlers = it.lifecycleHandlers + LifecycleHandler(
        id = id,
        event = event,
        onEvent = onEvent
      ))
    }
    onDispose {
      navigator.update { dest ->
        dest.copy(lifecycleHandlers = dest.lifecycleHandlers.filter { it.id != id })
      }
    }
  }
}

👉 showcase/common/src/iosMain/kotlin/dev/ahmedmourad/showcase/common/navigation/LifecycleHandler.kt at 83cc58c29acc2dc3bbcc4de0644e79e74826514e · AhmedMourad0/showcase

 

🤔 まとめ

まずは、ここらから記述していくのがいいのではないか。


@Composable
@NonRestartableComposable
fun DisposableLifecycleEffect(
  lifecycle: Lifecycle = LocalLifecycleOwner.current.lifecycle,
  onResume: () -> Unit,
  onPause: () -> Unit,
) {
  DisposableEffect(lifecycle) {
    val observer = LifecycleEventObserver { _, event ->
      when (event) {
        Lifecycle.Event.ON_RESUME -> onResume()
        Lifecycle.Event.ON_PAUSE -> onPause()
        else -> { }
      }
    }
    lifecycle.addObserver(observer)
    onDispose {
      lifecycle.removeObserver(observer)
    }
  }
}

👉 florisboard/app/src/main/kotlin/dev/patrickgold/florisboard/lib/compose/DisposableLifecycleEffect.kt at 35fd70ce6d2de05cf8be44fcd9a16b3b6d537453 · florisboard/florisboard

記述位置は Screen。

@NonRestartableComposable の意味を考えながら書く。

フォアグラウンド、バックグラウンドの切り分けにも使えるか。


【Android Studio】 エディタからの左のファイル表示(Project View) を同期させる

Android Studio の更新や

プロジェクトの設定時に、

毎回設定しているのですが、

毎回忘れて調べているので。

 

🧑🏻‍💻 以前は「Autoscroll from source」だった

👉 Android Studioでエディタでファイルを表示したらProject Viewでも表示する方法 #AndroidStudio - Qiita

Project View に設定の歯車ボタンがなくなってる!!

 

🧑🏻‍💻 「Always Select Opened File」

いつ変更になったか知らんが、

毎回探すのに苦労しますわ。


Project View
  ↓

右クリック

  ↓

Behavior

  ↓

Always Select Opened File

 

🧑🏻‍💻 まとめ

「shift → shift」 から「always」で調べると早い。

GUI操作の変更は調べづらい。


Android Studio 上で実機を操作できる件

Android Studio でエミュレーターのように接続した実機を、

操作、キャプチャーなどできるようになったんですね。



scrcpy も不要となってたんですね。

👉 【macOS / Android】scrcpy on Mac can't find "… libmbedcrypto.14.dylib" · Genymobile/scrcpy


Android Studio キャッシュの種類とそれぞれのクリーンの操作

ビルド速度が遅いので、


org.gradle.caching=true

としたら、署名付きビルドが更新されない。

「キャッシュ」のせいなのは分かるが、

いろいろ種類がありそうだよな、

Android Studio の「キャッシュ」って。

 

🤔 キャッシュの種類

ChatGPT に聞くと、3つあるそうです。

もちろん信用はしてないです。

IDE キャッシュ
コード補完、シンタックスハイライト、プロジェクトインデックス、ビルド設定など

Gradle キャッシュ
依存ライブラリ、ビルドキャッシュ(Gradleがダウンロードしたファイルなど)

ビルドキャッシュ
build/ フォルダ内の成果物やコンパイル済みファイルなど

そして、今回の調べるきっかけとなった


org.gradle.caching=true

は「ビルドキャッシュ」のようです。

 

🤔 まとめ

いきなり、分かったことをまとめます。


org.gradle.caching=true

は、ビルドキャッシュ。

キャッシュの範囲は、


ビルドキャッシュ < Gradle キャッシュ < IDE キャッシュ

のようなので、

キャッシュを消したいときは、


[Build] - [Clean Project]


[Build] - [Rebuild Project]


./gradlew clean


rm -rf ~/.gradle/caches


[File] → [Invalidate Caches / Restart]


rm -rf ~/.gradle

を上から順番に試していくのがいいと思います。

もちろん信用はしてません。

公式読むと良いです。

👉 Build Cache - docs.gradle.org

 

🧑🏻‍💻 おまけ

今回のような場合、

Gradle タスクに追加しておく方法もあるらしい。


tasks.withType(Sign).configureEach {
    outputs.cacheIf { false }
}

記述場所としては、

基本的に、android {} の外、末尾に記述する。

マルチモジュールなら subprojects {} 内に。


負荷ベンチマークツール wrk てシンプルで良いなあ

👉 WordPress で cf-cache-status: BYPASS のままで HIT にならない

シンプルで使いやすい感じ。


❯ wrk -v
wrk 4.2.0 [kqueue] Copyright (C) 2012 Will Glozer
Usage: wrk <options> <url>
  Options:
    -c, --connections <N>  Connections to keep open
    -d, --duration    <T>  Duration of test
    -t, --threads     <N>  Number of threads to use

    -s, --script      <S>  Load Lua script file
    -H, --header      <H>  Add header to request
        --latency          Print latency statistics
        --timeout     <T>  Socket/request timeout
    -v, --version          Print version details

  Numeric arguments may include a SI unit (1k, 1M, 1G)
  Time arguments may include a time unit (2s, 2m, 2h)


❯ wrk https://android.benigumo.com
Running 10s test @ https://android.benigumo.com
  2 threads and 10 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency   237.81ms  226.51ms   1.77s    88.62%
    Req/Sec    27.29     15.51    70.00     61.59%
  480 requests in 10.05s, 47.63MB read
  Socket errors: connect 0, read 0, write 0, timeout 2
Requests/sec:     47.77
Transfer/sec:      4.74MB

Lua とか合わせて使えるらしいので便利だろうと思えます。

👉 wg/wrk: Modern HTTP benchmarking tool