この記事はand factory Advent Calendar 2020の9日目の記事です。
昨日は@hagmonさんのエンジニアリングマネージャーについて考えるでした。
メンバーの自走のサポートを中心に考えるマネジメントで、上位者、下位者ともに能力を発揮できる良い組織になりそうですね😊😊(自社自賛)
はじめに
お世話になっております。
1年くらい前にDaggerを導入しようとして「なんもわからん・・・」となり、
一時Koinに甘えたりしていた者です。近頃、Dagger-Hilt、並びに同ライブラリのAndroidStudio上でのサポートなどが新たに登場し、情勢の変化が起きております。
これらの事情を踏まえ、弊開発環境でも今一度Google社製DIライブラリとの親善に向き合うため、アドベントカレンダーの舞台を機会として筆を執っております。
という感じでこういう記事です。↓
- はなすこと
- 「Hiltのチュートリアルをやってみたよ」
- はなさないこと
- 「DIってなんやねん」系のアレ
- Koinについて
- 別ライブラリからの移行とかの話
(Koinからの移行は供給が少なそうな気がするので、そのうちやったら別で書いてみたい)
「わからんかった」人間の記事なので、ツッコミどころがあればなんなりとマサカリを頂けると助かります。本当に。
導入
ほとんど思考停止でやっておく部分です。
// きっと他にも書いてある
ext.hilt_version = '2.28-alpha'
dependencies {
// きっと他にも書いてある
classpath "com.google.dagger:hilt-android-gradle-plugin:$hilt_version"
}
バージョンは良い感じのを選んでくれよな!👊(まだalphaしかないんすねって思いました)
// きっと他にも書いてある
apply plugin: 'kotlin-kapt'
apply plugin: 'dagger.hilt.android.plugin'
// きっと android {...} とかが書いてある
dependencies {
// きっと他にも書いてある
implementation "com.google.dagger:hilt-android:$hilt_version"
kapt "com.google.dagger:hilt-android-compiler:$hilt_version"
}
ここまで外部依存関係系。
@HiltAndroidApp
class MyApplication : Application() {
// きっと他にも書いてある
}
で、Applicationクラスへのアノテーションで、アプリ全体へのコンテナ(Component)を生成してくれる様子。
基本的な使い方
「Fragmentで使うクラス、生成の処理書くんメンドいなあ」
「Hiltくん経由で生成して注入できるで」
@AndroidEntryPoint
class LogsFragment : Fragment() {
@Inject lateinit var dateFormatter: DateFormatter
// 他の記述
}
Androidのクラス(ちゃんとした対象一覧はここ)に@AndroidEntryPoint
アノテーションを付けると、そのクラスのライフサイクルに従うDIコンテナを生成してくれます。(このあたり、Daggerからかなり楽になった所っぽいです)
その上で、@Inject
でフィールドインジェクションが可能です。
注意として、@Inject
の対象はprivate
にすることが出来ません。
「そんで、注入したいクラスの生成方法もちゃんと書いといてな」
class DateFormatter @Inject constructor() {
// 他の記述
}
コンストラクタに@Inject
をつけることでHiltがクラスの生成方法を理解します。
コンストラクタにパラメータがある場合は、パラメータのクラスのコンストラクタを...と続けて依存グラフを生成します。(このあたりはDI自体の概念として基本)
とりあえずこれで一番シンプルな利用ができます。
インスタンスの共有
「このInjectいうやつ、毎回新しいインスタンスが生成されるん?」
「せやで」
「シングルトンとかで扱いたいんやけど」
@Singleton
class DateFormatter @Inject constructor() {
// 他の記述
}
いわゆるスコープの件です。スコープも、生成対象にアノテーションを付けて表現します。
他にも@ActivityScoped
や@FragmentScoped
などがあります。一覧はここ。
ちなみに、@Singleton
は、実際のところ「Applicationレベルのスコープ」と言えます。
コンストラクタ以外を利用した注入
「ファクトリメソッドとか通さないといけないフィールドだと、コンストラクタ呼べなくて登録できひん・・・」
「Moduleいうのを使うんよ」
@InstallIn(ApplicationComponent::class)
@Module
object DatabaseModule {
@Provides
fun provideLogDao(database: AppDatabase): LogDao {
return database.logDao()
}
}
@Module
をコンテナとして、その中に関数のイン・アウトとして生成方法を示します。
このときの関数に、@Provides
アノテーションを使用します。
Interfaceに対する実装を利用した注入
「フィールドの型にはインターフェースを指定して、インスタンスはその実装を使う時はクラスが違うやん。どうすんの?」
「それもModuleやけど、専用のアノテーションがあるね」
@InstallIn(ActivityComponent::class)
@Module
abstract class NavigationModule {
@Binds
abstract fun bindNavigator(impl: AppNavigatorImpl): AppNavigator
}
@Module
を付与した抽象クラスの中で、抽象関数として入力:実装/出力:インターフェースの定義をします。
このときに@Bind
アノテーションを使用します。
@Provides
と似ていますが、@Bind
は抽象関数にしか使えません。よって、親のModuleクラスも抽象クラスである必要があります。(同一のModuleに両方定義できません。)
@Provides
で入力:実装/出力:インターフェースの関数を用意して、入力をそのままreturn
する関数を実装しても同じことになるかと思います。
そのほか
Componentクラスを作らなくて良い
@AndroidEntryPoint
アノテーションを付けると、そのクラスのライフサイクルに従うDIコンテナを生成してくれます
と記載しましたが、Androidで使いそうなスコープごとのComponentは標準で用意されています。
それぞれ、どのコンテナに属するかを記載すれば利用できるようになります。
(ActivityやFragmentは@AndroidEntryPoint
により自動、Moduleはしれっと書いていた@InstallIn
アノテーションにより「どのコンテナに属するか」を示します)
Qualifierをクラスで分けることができる
同じクラスを指定しつつ、別のインスタンスを利用したい場合の話です。
Daggerでは文字列で名称を指定して別のインスタンスであることを示していましたが、
Hiltではアノテーションクラスを定義して、それを付与することで区別します。
こっちのが型安全的で好きですね。
参考
Google Codelabs android-hilt
https://codelabs.developers.google.com/codelabs/android-hilt
あとがき
正直に言いまして、ここ2日くらいで初めてHiltについて学びました。
そして手を付けてみたら具体的なプロジェクトに使えていないこともあり「え、簡単では・・・?」となり、
「逆にこれDaggerとの比較の観点が無いと何が嬉しいのか説明できねえやつだわ」と困ったりしていました。
結果、少しDagger側の話も学び直したりもしまして、もしかしたら今なら1年前よりもMaster of Daggerがすんなり読めるのでは・・・?とか思っています。勢いで参加に名乗りを上げたアドカレでしたが、またひとつ勉強の機会になったと思います。ありがとうございました。