Kenji Abe

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 として取得することも可能です。

--

--

Photo by Chris Lawton on Unsplash

Navigation Composeでも NavOptions が設定でき、画面遷移を細かく制御できるようになっています。この NavOptions の挙動についてまとめておきます。

NavGraph

今回使用するNavGraphの定義は以下のようなシンプルな構成を考えます。

launchSingleTop

オプションなしの画面遷移では、バックスタックのトップにある(今表示されてる)画面から同じ画面に遷移した場合、同じ画面が上に積まれることになります。遷移後にバックキーを押すと同じ画面がまた表示される感じです。

navController.navigate("screen_a")

--

--

Kotlinの data classcopy を使うときに気をつけないと意図しないデータ不整合が起きる可能性があります。Coroutinesを使ってると陥りやすい罠かなと思います。

現象

想定としてAndroidのComposeを考えます。他のケースでも同様です。

以下のような、Coroutinesを使ってユーザー一覧を別スレッドで取得する処理があるとします。

この処理をViewModelで以下のように取得し、UiStateを copy を使って更新するとします。また、UiStateの別プロパティの取得処理もするとします。

この状態で、ComposeのコードからViewModelの処理を連続で呼び出します。

期待する結果としては、ユーザー一覧とフラグtrueがUiStateに設定されてることだと思います。

しかし、結果は以下のように想定しないものになります。

uiState = UiState(users=[], flag=true)  <- フラグ更新後uiState = UiState(users=[User(...)], flag=false) <- ユーザー一覧取得後

最初に、フラグを更新結果がログに出力されて、次にユーザー一覧取得後の結果がログに出力されています。

しか、フラグは最初は正しいですが、ユーザー一覧取得後には元に戻ってしまっています。

原因

原因としては ユーザー一覧の取得処理です。ここでは copy 処理の中でCoroutinesの別スレッドの処理を呼び出しています。

そのため、処理前のUiStateの状態をキャプチャして、別スレッドの処理が完了後にその状態をコピーするようになっています。

そのため、この別スレッドの処理の間に何か状態が変更されたとしても、反映されないため巻き戻ってしまうことになります。

解決方法

この解決方法はいたって簡単です。 copy 処理の中で別スレッド処理をしないようにするだけです。なので、copy 前に処理するだけです。

これだけで大丈夫です。

Coroutinesが別スレッドで実行されてることを意識せずコードを書けるの非常に便利ですが、こういった罠があるため注意が必要です。

--

--

Kenji Abe

Kenji Abe

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