Kenji Abe

Photo by Possessed Photography on Unsplash

Composeの画面遷移で、前の画面に結果を返す方法についてまとめておきます。

基本的にはFragmentと同じような感じで NavBackStackEntrySavedStateHandle を使用していきます。

結果を返す

ScreenAからScreenBに遷移して、ScreenBでの画面の結果をScreenAに返す処理です。

navController.previousBackStackEntry?.run {
savedStateHandle["result"] = "Foo"
}

上記のように NavHostController#previousBackStackEntrySavedStateHandle にKey-Valueで設定するだけです。

previousBackStackEntry っていうのが大事で前の画面の NavBackStackEntry アクセスしています。

結果を受け取る

今度はScreenA側でScreenBの結果を受け取る方法です。

val result: String? = navController
.currentBackStackEntry?.savedStateHandle?.get("result")

受け取るには NavHostController#currentBackStackEntrySavedStateHandle から取得することが出来ます。

受け取るときは currentBackStackEntry を使います。

結果の削除

結果を受け取るときの注意としては、基本的にセットした結果は自分で削除しない限りは残り続けます。画面回転等でも残ります。
そのため、結果がセットされた後にRecompositionが行われると何度も結果を取得することが出来ます。

UI状態して同じ結果が返りつづけるのを期待している場合は問題ないかもですが、もし、結果によって一度だけ処理したい場合は対応が必要になります。例えば、結果を受け取ったら画面をリフレッシュするとかの場合です。

ワンショットの処理が行いたい場合は以下のように処理後に remove で削除することで何度も呼び出されることはなくなります。

navController.currentBackStackEntry?.run {
if (savedStateHandle.contains("result")) { // 結果があるか確認
// 結果取得
val result: String? = savedStateHandle["result"]
// 結果を使った処理
viewModel.someAction(result)
// 結果を削除
savedStateHandle.remove<String>("result")
}
}

また、 remove は削除前にセットされてる値を返すので、以下のような書き方もで結果の取得と同時に削除することもできます。

val result: String? = navController
.currentBackStackEntry?.savedStateHandle?.remove("result")

ただ、個人的には確実に処理後に結果を削除したほうが良いと思うので、前者の実装のほうが良いかなと思います。

Recompositionへの対応

前述までの方法でも問題ないのですが、ComposeはRecompositionにより何度も処理が実行される可能性があります。
そのため、これまでの実装だとRecompositionごとに画面の結果があるかを確認することになります。

LaunchedEffect 等で一度だけ実行されるようにしておくと良いと思います。

LaunchedEffect(Unit) {
navController.currentBackStackEntry?.run {
if (savedStateHandle.contains("result")) {
// ...
}
}
}

参考

--

--

Photo by Alex Padurariu on Unsplash

Accompanistadaptive というライブラリが追加されました。これを使用して2ペインのレイアウトを組むことができます。この実装方法について解説していきます。

注意: AccompanistはExperimentalなものなので今後変更される可能性があります。

実装方法

build.gradleaccompanist-adaptive を追加します。

dependencies {
implementation "com.google.accompanist:accompanist-adaptive:<version>"
}

単純な半分で左右に分割する実装です。

TwoPane のComposable関数を使っていきます。

first は左側の部分、 second は右側の部分の実装になります。この引数に表示したいComposable関数を記述します。

strategy は後で解説しますが、 TwoPaneStrategy を指定します。ここでは左右に半分に分割する指定になります。

displayFeatures は後で解説します。

これを実行すると以下のようになります。

--

--

Photo by Tobias Cornille on Unsplash

Composeにおいてrecomposition後も状態を保持するために remember がありますが、画面回転などConfiguration Change後でも状態を保持する rememberSaveable について解説します。

rememberSaveable

単純な値で rememberSaveable を使う場合は remember とほぼ変わりません。

また、 mutableStateOf も同様に使用できます。

これだけでConfiguration Change後も値を復元してくれます。

使用できる型

rememberSaveable はすべての型が使用できるわけではありません。

例えば、上記のように独自のクラスを rememberSaveable で使おうとすると、以下の例外を投げます。

java.lang.IllegalArgumentException: User(name=) cannot be saved using the current SaveableStateRegistry. The default implementation only supports types which can be stored inside the Bundle. Please consider implementing a custom Saver for this class and pass it to rememberSaveable().

rememberSaveable は最終的に ComponentActivity#onSaveInstanceState にて状態を保存するため、 Bundle に保存できるもだけが使用できます。

サポートされてる型は ココのコード で定義されています。
また、MutableState については特別な処理で対応していて、MutableStateで使われてる型のほうがチェックされます。

rememberSaveable に対応する

rememberSaveable に対応するにはいくつかの方法があります。

Parcelable

一番簡単な方法は、独自クラスを Parcelable にするだけです。

これでクラッシュせず rememberSaveable でも使用可能になります。

Saverの実装

単純に Parcelable に対応できない場合は Saver を実装することで対応することもできます。

SaverBundle に保存できる型に変換して、Bundle から復元した値を変換する処理を実装します。 実装したものをrememberSaveablesaver パラメータに渡すことで対応できます。

MutableState を使う場合は stateSaver パラメータのほうに実装した Saver を渡します。

--

--

Photo by Nick Fewings on Unsplash

Android 13からOpt inにて Predictive back gesture が使用できるようになります。これにより、バックジェスチャー(画面の左から右にスワイプするやつ)でホームに戻るときに予測できるようになります。

どのような感じは以下のドキュメントで確認できます。戻るときにホーム画面が少し見えるような感じになります。

https://developer.android.com/about/versions/13/features/predictive-back-gesture

ただし、この Predictive back gesture によりバック関連の既存挙動が変更され、場合によってはかなりの変更が必要になります。

このドキュメントには以下のように記載されており、今後デフォルトとなり対応が必須になってくると思われます。

Caution: If you don’t update your app by the next major version of Android following 13, users will experience broken Back navigation when running your app.

Predictive back gestureのOpt in

Android 13の時点ではOpt inが必要になります。以下のように AndroidManifest.xml に追加します。

<manifest>
<application
...
android:enableOnBackInvokedCallback="true"
...
>
</application>
</manifest>

また、端末/エミュレータでは開発者オプション から有効にする必要があります。 Predictive back animations ( 日本語では 予測型「戻る」アニメーション )を有効にする必要があります。

既存挙動の変更

Predictive back gesture が有効になると既存挙動が変更されます。
onBackPressedKeyEvent.KEYCODE_BACK が機能しなくなります。

この例では、 onKeyDown を使ってますが、 onKeyUp 等でも同様です。

こういった処理は結構使用されてると思うので、影響が大きいアプリはありそうです。早めに対応を検討していったほうが良いと思います。

Predictive back gestureのサポート

Predictive back gestureのサポートをするにはPlatform APIかAndroidXを使用する必要があります。minSdk 33っていうアプリはだいぶレアかと思いますので、AndroidXを使っていくことになると思います。

今回はAndroidXの使用方法を紹介していきます。Platform APIは省略します。

androidx.activity:activityOnBackPressedCallback を使うことで対応できます。これを書いてる時点では 1.6.0-alpha05 を使用しています。

Composeの場合

Composeの場合は BackHandler を使うことで対応可能です。

注意

例えば、条件でActivityを終了させたりしなかったりするパターンで以下のような実装を思いつくと思います。

ただ、この場合に Predictive back gesture をOpt inしていたとしても、挙動が以前と同じなってしまい効果がなくなってしまいます。

なので、条件によって終了させるかどうかを実装するときは、 OnBackPressedCallback の有効・無効を切り替えるほうが良いと思います。

無効のときは Predictive back gesture の効果が得られつつ、Activityを終了させることができます。

Composeの場合はフラグ用意して、それを切り替えて制御します。

--

--

Photo by Carolina Garcia Tavizon on Unsplash

Material 3のColor Systemには Surface tones というものがあります。この仕様によって想定した色をうまく設定できないなど気づきにくいこともあります。この Surface tones についてComposeの実装を踏まえて簡単にまとめておきます。

これ書いてる時点ではCompose Material 3はalphaなので今後変更の可能性あります。

Surface tones

項目の直接のリンクがないのですが、上記のドキュメントの中盤あたりに、 Surface tones という項目があります。

Material 3におけるSurfaceにはelevationのレベルがあり、+1から+5まであります。このelevationのレベルによって色調が変化する仕様になっています。

ドキュメントのほうにコンポーネントごとのレベル例が記載されています。例えば、AppBarやBottomBarは+2という感じになっています。

androidx.compose.material3

Composeの実装である、 androidx.compose.material3 にもこの仕様が入っています。

今回は alpha14 を使っています。

androidx.compose.material3.Surface には tonalElevation のパラメータがあり、これを設定することでelevationレベルを設定する感じになります。デフォルは 0dp になっています。

また、この tonalElevationcolorMaterialTheme.colorScheme.surface のときのみ有効になります。

以下は tonalElevation の値による色の違いになります。値が大きいほど色が濃くなっています。surfaceの色は Color(0xFFD0BCFF) を指定してます。

--

--

M1 Macbook Proを買ったけど、そのとき色々困ったので、そのときのメモ。

なんか気づいたことあれば追記していく。

環境

  • 16インチMacBook Pro
  • Apple M1 Pro
  • macOS Monterey

原因がM1なのかMontereyなのかはよく分かってない。

キーボードショートカット

Move focus to next window が以前と変わってて、これのせいで問題起きる。

デフォルトは command + @ になってるので、これを command + F1 にする。

これのせいで、これまで出来ていた command + F1 でのウィンドウ切り替えと、Visual Studio Code で command + shift + [ が効かなくなってる。

スリープ問題

Bluetoothのマウスやキーボードがあると、スリープにしてもすぐに復帰するようになるっぽい。

はっきりと解決はしてないが、以下のことをしておいた。

スリープしたらすぐにパスワードを要求するように設定。 Require passwordimmediately にする。

--

--

Composeでの WindowInsets の使用方法についてまとめます。

これまでは accompanist/insets として提供されていましが、こちらがDeprecatedとなり、Compose 1.2から公式に WindowInsets の対応が入っています。

WindowInsets はedge-to-edgeの対応で使います。edge-to-edgeについては以下のドキュメントを確認してみてください。

https://developer.android.com/training/gestures/edge-to-edge

また、 WindowInsets はIMEアニメーションでも使用します。IMEアニメーションについては以下のドキュメントを確認してください。

https://developer.android.com/training/system-ui/sw-keyboard

準備

まずThemeは以下のように設定します。

メインのActivityでは以下のコードを実行しておきます。

WindowCompat.setDecorFitsSystemWindows(window, false)

Modifier.windowInsetsPadding

WindowInsetsは Modifier の拡張関数として、 windowInsetsPadding があるので、こちらを使用していきます。これは引数に指定したWindowInsetsのスペースを確保します。

この例だと、Boxの中にBoxが配置してあり、中に配置してるBoxにPaddingが設定されます。

windowInsetsPadding の引数にはどのスペースを設定するかを指定します。

いくつかあるのですが、よく使うものだと、 safeDrawingsafeGesturessafeContent などがあります。

safeXXXっていうのを使えばほとんどケースを対応できるかと思います。以下に実行例を示しておきます。

  • safeDrawing: ステータスバー、ナビゲーションバー、IMEのスペース
  • safeGestures: システムが使用するジェスチャ領域など
  • safeContent: safeDrawing + safeGestures

基本は safeDrawing を使いつつ、ジェスチャなどの処理が入る場合にその他を使用する感じなるかなと思います。

また、これらをもっとシンプルに書けるAPIもあります。

safeDrawingPaddingsafeGesturesPadding などを使えばシンプルに書けます。

部分的に使用する

例えば、 TopAppBar に上のスペースだけWindowInsetsを適用したい場合は、以下のように実装することで対応可能です。

only を使って WindowInsetsSides で位置を指定すれば、そこだけのスペースが適用されます。

また、 + を使うことで、複数の位置を指定することも可能です。以下の例は上と横のスペースが適用されます。

その他

他にもWindowInsetsを PaddingValues として取得することも可能です。

--

--

Kenji Abe

Kenji Abe

Programmer / Gamer / Google Developers Expert for Android, Kotlin / @STAR_ZERO