Dagger 2.31より、AssistedInjectのサポートが入りました。このAssistedInjectをViewModelで使用する方法です。

画面の引数などをDaggerのInjectと組み合わせて、ViewModelのコンストラクタに渡したい場合があったりします。
こういったケースではAssistedInjectを使うことで可能になります。
(他には SavedStateHandle を使う方法もあります。)

build.gradle

2.31のHiltを使っていきます。

ViewModel

以下のようなコンストラクタを持つViewModelを例にします。

1つ目は通常のDaggerモジュールなどに定義されたオブジェクトで、2つ目で SavedStateHandle も受け取るようにします。最後に画面から渡したい引数になります。

今回は SavedStateHandle も使ってますが、不要なら無くても大丈夫です。

AssistedFactory

画面から渡したいものを AssistedFactory として定義します。

画面からコンストラクタに渡したいものを引数にして、Injectしたいクラスが戻り値になってるメソッドを定義したinterfaceを作ります。

interfaceには @AssistedFactoryのアノテーションを追加します。

ViewModelFactory

次にViewModelFactoryを定義しておきます。

今回は SavedStateHandle も使ってるので AbstractSavedStateViewModelFactory を使っています。もし不要なら ViewModelProvider.Factory も使えます。

ViewModelのインスタンスは AssistedFactory のメソッドから作成して返します。

AssistedInject

コンストラクタを AssistedInject の設定をします。

AssistedInjectしたい引数に @Assisted を追加します。
ただし、通常のモジュールからInjectするものは無くて大丈夫です。

コンストラクタには @Inject の代わりに @AssistedInject を追加します。

画面から値を渡す

最後に画面から値を渡すようにします。


Image for post
Image for post
Photo by Moritz Knöringer on Unsplash

今年もAndroid開発状況を個人的な観点からまとめたいと思います。
去年のはこちら

Kotlin

Kotlinはもう言うことは特にないですね。

個人的には KSP がどうなるかが気になるところですね。

Coroutines

Android11のリリースと合わせて正式にCoroutinesが推奨される非同期処理となりました。(あとAsyncTaskがDeprecatedになりましたね)

これまで以上にCoroutinesを使っていく場面が増えてくると思いますし、Jetpackライブラリでも当たり前のように使われていくと思います。

また、StateFlowSharedFlowなどの便利なものも出てきています。

まだ触ったことない方はぜひチャレンジしてみてください。

https://developer.android.com/kotlin/c …


Dagger AndroidからHiltを段階的に移行する方法です。最初のいくつかの設定をすれば段階的に移行することができると思います。

サンプルプロジェクトを実際に段階的に移行したので、各ステップごとにその差分を見ながら確認してもらえると良いかなと思います。

サンプルプロジェクト

マルチモジュール構成のシンプルなサンプルになっています。

Componentの引数を不要にする

差分

@Component.Factory で、 @BindsInstance で引数をもらってる場合は、この引数を使わないようにします。HiltではComponentが不要になるので、事前に対応しておきます。
Context についてはHiltでも扱えるので、これはそのままでも大丈夫です。

これに対応する方法としては、 DaggerのModuleからApplicationクラスにアクセ …


Image for post
Image for post
Photo by Brett Jordan on Unsplash

Fragmentのドキュメントが刷新されましたが、そこに次のように記載されています。

https://developer.android.com/guide/fragments/create#add-programmatic

Note: You should always use setReorderingAllowed(true) when performing a FragmentTransaction. For more information on reordered transactions, see Fragment transactions.

setReorderingAllowed を常に使うべきと記載されています。後方互換のためデフォルトではfalseになっています。

この setReorderingAllowed によってライフサイクルがどのように変わるかを見ていきます。

環境

使用したのは、 1.3.0-beta02 です。

implementation "androidx.fragment:fragment-ktx:1.3.0-beta02"

New State Manager

Fragmentの管理方法の内部実装が新しくなってるのですが、これにより setReorderingAllowed の挙動も影響を受けます。

New State Managerについては以下の記事にあります。

Fragment 1.3.0-alpha08からこちらはデフォルトで有効になっています。

以下のコードを先に実行しておくことで、以前の方法として実行することもできます。

FragmentManager.enableNewStateManager(false)

このNew State Mangerが有効、無効での挙動の違いも見ていきます。

この記事ではNew State Manger / Old State Manger と表現します。

単純なFragment遷移

以下のように単純にFragment遷移をするときの違いです。
FragmentA から FragmentBへ遷移します。

単純な遷移では特に影響はないと思いますが、 Shared element transitions などで postponeEnterTransition を使う場合は必ず setReorderingAllowed(true) にする必要があります。そうしないとアニメーションが奇妙な感じなります。

New State Mangerの場合

New State Mangerの場合は setReorderingAllowed に関わらず同じ順番で実行されます。


ConcatAdapterGridLayoutManager を使うときに、Adapterによって列数を可変にする方法です。

例えば、以下のような画面です。

Image for post
Image for post

実装方法

実装は結構簡単にできます。

getItemViewType

まず、各Adapterでは getItemViewType を実装して他のAdapterとカブらない値を返します。一番良いのはレイアウトIDを返す方法だと思います。

GridLayoutManager.SpanSizeLookup

次に GridLayoutManager の設定です。 GridLayoutManager.SpanSizeLookup を使って動的にSpanサイズを設定することができます。

ConcatAdapter からpositionごとに viewType を取得して、SpanSizeを設定していきます。この例だと、 R.layout.item_one_column の場合は1列表示で、その他の viewType は2列で表示する設定します。

isolateViewTypes

これだけでは、期待通りの挙動にはなりません。

ConcatAdapter はデフォルトでは viewType は Adapter ごとに0からインクリメントされた値を返します。
例えば、Adapterを3つ使ってる場合、ConcatAdapter の viewType は0, 1, 2を返します。

このviewTypeを各Adapterで設定された値を返すようにするには、以下のように ConcatAdapter.Config の設定をする必要があります。

isolateViewTypes にfalseを設定することで、各Adapterで設定されたviewTypeを返してくれるようになります。

これだけで、複雑な GridLayoutManager も実装することが可能になります。

注意

isolateViewTypes の本来の用途としては、ConcatAdapter で使用されてる各AdapterでのViewHolderを共有するものになります。
詳しくは以下の記事を参照。

そのため、もし共有されて困るような場合があれば、別の方法が必要かもしれません。


ColorStateList を使用するときの注意点です。いくつかハマったので残しておきます。

ColorStateListについて

まず、ColorStateList に簡単に説明です。

res/color にxmlファイルとして定義することが出来ます。

これをButtonの android:textColor に設定することで、押したときにテキストの色を変更できます。

また、すこし別の使い方として、既存の色に android:alpha の値を設定して新たに色の定義として使用することができます。

似たようなものに StateListDrawable があります。こちらはColorではなくDrawableになります。

この ColorStateList を使ったときの注意について解説していきます。

コードから設定するとき (Android 5.x以下)

レイアウトXMLで設定するときには基本的に問題はならないですが、コードから設定するとき、且つ Android 5.x以下 の場合に注意が必要です。

例えば、以下のようにThemeのattributeを使って色を指定しているときです。 ?attr/colorPrimary の箇所ですね。これは半透明にしてます。

このとき、 ContextCompat.getColorStateList で取得して設定すると Android 5.x 以下の場合はThemeが反映されません。なので、期待している色を取得できません。

ドキュメントにも記載されていますが、23以降しかThemeが反映されません。

Starting in Build.VERSION_CODES.M, the returned color state list will be styled for the specified Context’s theme.

これを解決するには、AppCompatResources.getColorStateList を使います。これを使うとThemeが反映された状態で色を取得するので、期待する色が取得できます。

Android 5.xのサポートは減ってきてるかもですが、使用する際は気をつけてください。

Backgroundに設定する場合

これはどのAndroidバージョンでも注意が必要です。

同じく半透明にする ColorStateList を使います。


SingleLiveEventの代わりにSharedFlowを使ってイベント通知

Image for post
Image for post
Photo by Adam Solomon on Unsplash

追記: まだまだ検討する必要があります。この記事は参考程度にしてもらえると助かります。

これまでViewModelからViewへのイベント通知を行うには、SingleLiveEvent がよく使われてきました。

これに代わる手法を個人的に色々模索してたのですが、Coroutines 1.4で導入された SharedFlow が使えるのではないかと思っています。
まだ実際にプロダクションに投入してるわけではないので、なにか見落としてることはあるかもです。そのときは指摘してもらえると嬉しいです。

SingleLiveEventの問題点

SingleLiveEventLiveData を改造したものになり、そもそもの LiveData の挙動から大きく異なるものになります。

こちらの IssueTracker ではそういった理由で Je …


Coroutines 1.4.0-M1SharedFlow が入ったので、使い方を紹介します。

Image for post
Image for post
Photo by Fidel Fernando on Unsplash

SharedFlow?

SharedFlow は名前の通り Flow ですが、普通の Flow は Cold Stream に対して、こちらは Hot Stream になります。Flow と異なり Complete することはないのです。

また、キャッシュとバッファリングに対応することができます。

SharedFlow で発行された値は collect (サブスクライブ)してる箇所すべてに通知されるます。

ユースケース

以下のIssueのdescriptionを見てみてください。

同期的なイベントリスナーとか、イベントをキャッシュとかリプレイするとか、みたいな感じですかね。

個人的に、Androidでは LiveData を使ってイベント通知(SingleLiveEvent)などやってたと思いますが、それの代わりになりそうだなぁって思って …


Kotlin 1.4で改善されたSAM変換についてまとめます

Kotlin interfaceのSAM変換

これまでKotlinのinterfaceはSAM変換できず、以下のように object キーワードを明示的に使う必要がありました。
(Java interfaceの場合はこれまでもSAM変換できていました)

Kotlin 1.4ではKotlin interfaceでもSAM変換が可能になりました。ただし、そのままで使えず、interfaceに fun 修飾子を付ける必要があります。

Java interfaceの異なる引数のSAM変換

例えば、以下のようなJavaコードがあったとします。引数で2つのSAM変換可能なinterfaceを受け取ります。

これをKotlinから使うときはSAM変換できるのは、両方ともラムダ式にした場合のみでした。片方がオブジェクトになってる場合は片方をラムダにはできませんでした。

Kotlin 1.4からは片方がオブジェクトとラムダを混ぜることが可能になりました。

Android開発での影響

これの影響として、Android開発で使われるktxの拡張関数が一部不要になったりします。

例えば、LiveDataでobserveするときにktxを使うことで記述を楽にすることができました。

liveData.observe(this) { }

これがKotlin 1.4からはktxがなくても可能になります。

これに伴い、こちらの拡張関数はDeprecatedになる予定です。

他にもktx等の拡張関数が不要になるものがあるかもです。

KotlinでのJava SAM変換

これまではJava SAM変換を適用して、以下のような書き方ができませんでした。


Jetpack Composeでレイアウトを組むための基本的なまとめです

Image for post
Image for post
Photo by La-Rel Easter on Unsplash

Jetpack Composeでは、これまでのViewシステムとは違うためレイアウトを組むのに手こずったので、基本的なことを簡単にまとめました。

ConstraintLayoutについては今回は触れてません。

Composeのバージョンはalpha02を使っています。

レイアウトComposable

レイアウトを組むのに使用できるComposableです。これらを組み合わせてレイアウト組んでいくことになります。

Box

Box(
modifier = Modifier.preferredSize(120.dp, 60.dp),
backgroundColor = Color.Yellow,
gravity = ContentGravity.Center
) {
Text(text = "Sample")
}

About

Kenji Abe

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

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store