Kenji Abe

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

--

--

よくあるケースとして、別のActivityを起動して今開いてるActivityを閉じるというケースがあると思います。実はこのときに startActivityfinish を呼び出す順番で挙動が異なります。

先に startActivity を呼び出して次に finish を呼び出す場合は基本的に意図しない挙動にはなりません。

startActivity(intent)
finish()

逆に先に finish を 呼び出して startActivity を呼び出す場合は、実は意図しない挙動になっています。

finish()
startActivity(intent)

このとき、ActivityのIntentには FLAG_ACTIVITY_NEW_TASK が自動で追加されています。

呼び出し先のActivityに以下のようなコードを書いておくと確認できます。

この挙動が意図しない結果になる可能性があるため注意が必要です。

基本的に startActivity を呼び出してから finish を呼び出すようにしたほうが安全だと思います。

参考

--

--

Photo by Possessed Photography on Unsplash

Lifecycle ViewModel 2.5.0-alpha03 で InitializerViewModelFactory が追加されています。この InitializerViewModelFactory を使ってViewModelを生成する方法を紹介します。

ここでは CreationsExtras についても出てくるので、前回書いた記事を参考にしてください。

InitializerViewModelFactory を使うとシンプルにViewModelFactoryを生成することができるようになっています。

※これを書いてる時点ではalphaなので今後変更があるかもです。

環境

  • androidx.lifecycle:lifecycle-viewmodel-ktx:2.5.0-alpha03
  • androidx.fragment:fragment-ktx:1.5.0-alpha03

Composeの場合

  • androidx.activity:activity-compose:1.5.0-alpha03
  • androidx.lifecycle:lifecycle-viewmodel-compose:2.5.0-alpha03

簡単な使用例

例としてコンストラクタ引数を一つ持ったViewModelを生成してみます。Activityから生成することを想定します。

InitializerViewModelFactory は直接使用することはできないため、 InitializerViewModelFactoryBuilder を使って InitializerViewModelFactory を作成します。

addInitializer の中で単純にViewModelのインスタンスを作っています。このとき引数も渡しています。

あとはこのFactoryを ViewModelProvider を使ってViewModelを取得するだけです。

全体的なコードは以下のような感じになります。

viewModelFactory関数

viewModelFactory という関数が用意されてるので、そちらを使うともう少し簡単にFactoryを作れます。

このFactoryは複数のViewModelにも対応できます。

--

--

Photo by Alex Shute on Unsplash

Lifecycle ViewModel 2.5.0-alpha01 に CreationExtras が追加されました。この CreationExtras について使い方などを紹介したいと思います。

CreationExtrasViewModelProvider.Factory に追加の情報として渡すことのできるMapのようなものになっています。また、 ViewModelProvider.Factory が状態を持つことなく値を渡すことが可能なります。

※これを書いてる時点ではalphaなので今後変更があるかもです。

環境

  • androidx.lifecycle:lifecycle-viewmodel-ktx:2.5.0-alpha03
  • androidx.fragment:fragment-ktx:1.5.0-alpha03

Composeの場合

  • androidx.activity:activity-compose:1.5.0-alpha03
  • androidx.lifecycle:lifecycle-viewmodel-compose:2.5.0-alpha03

使い方

Activityで以下のようなViewModelを生成する例です。

単純な引数を渡す方法は以下のような感じになります。ちょっと長いですが。

CreationExtras.Key

CreationExtras.KeyCreationExtras で使用するKeyになります。型パラメータの型が実際に設定したい型になります。この場合はIntになります。

CreationExtrasの作成

CreationExtras を作成して値を設定するには MutableCreationExtras を使用します。Keyには CreationExtras.Key のものを指定します。

MutableCreationExtras のコンストラクタにはマージしたい別の CreationExtras を指定できます。

コード例で CreationExtras 作成時に指定してる defaultViewModelCreationExtras はデフォルトで様々な値が設定されています。これを含めるために MutableCreationExtras のコンストラクタに渡しています。 defaultViewModelCreationExtras はまた後述します。

ViewModelProvider.Factory

ViewModelProvider.Factory は新しく CreationExtras を受け取る create メソッドが追加されいます。これを使って渡された値を取得できますので、それを使ってViewModelを作成します。

--

--

Photo by Denise Jans on Unsplash

Navigation Composeはそのまま使うとコード量が増えたり引数の扱いが面倒だったりします。こういった問題を便利にしてくれる compose-destinations というライブラリを紹介します。

このライブラリはNavGraphの設定を簡単にしてくれたり、Type-safe引数を実現してくれます。このライブラリはKSPを使ったコード生成をしています。

これを書いてる時点では 1.2.1-beta です。今後変更があるかもしれません。

簡単な使い方

まずは、簡単な使い方を紹介します。単純に次の画面に遷移するだけのものです。セットアップはREADME等を見てください。

最初に画面のトップとなるComposable関数に @Destination をつけて必要な実装していきます。簡単な説明はコード上にコメントしています。

最後にNavGraphを設定しますが、これはライブラリが生成した NavGraphsDestinationsNavHost に設定するだけです。

これだけで画面遷移の実装ができます。

引数を扱う

引数も簡単に扱うことができます。

受けとりたい引数をComposable関数の引数として指定するだけです。あとはコード生成で引数が渡されるDestinationが生成されるので、それに渡すだけです。

Parcelableの引数

Parcelableの引数も先程と同じように簡単に扱うことが可能になっています。

これはParcelableをBase64に変換することで実現しています。正直、Base64にして扱って良いものかは分からないのですが、ぼくが触ってる感じパフォーマンスが悪いとかはなかったです。

NavBackStackEntryなども引数に指定するだけで受け取れます。

その他

他にもアニメーションの対応や、NavGraphを細かく自分で設定できたりもします。

詳しくは Wiki 等を見てもらえると。

まとめ

compose-destinations について簡単に紹介しました。

個人的には便利に使えそうだなぁって思っています。今のところIssueなどの対応も早いので、なにか気になることあればIssueをあげると良いかなと。

あと個人的に気にしてることですが、諸事情で後から捨てるとなったとしても、なんとか対応できそうな感じなんで、とりあえず試すのもアリかなぁって思ってます。

気になる人は試してみると良いと思います。

--

--

Kenji Abe

Kenji Abe

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