この記事について
androidでHiltを使う方法をまとめます。
DI(依存注入)とは?
依存とはあるオブジェクトから他のオブジェクトを参照・使用することを指します。
以下の例ではCarクラスはEngineクラスに依存していることになります。
class Car {
var engine : Engine = Engine()
fun drive() {
engine.start()
}
}
class Engine {
fun start() {
...
}
}
UMLのクラス図とシーケン図では以下のように表されます。
DI(依存注入)とはあるオブジェクトから他のオブジェクトを参照・使用する関係(依存)を注入(あるオブジェクトと他のオブジェクトをつなげる)ことを指します。
Hiltでは以下の2種のDIをサポートしています。
- Constructor Injection
- Field Injection
Constructor Injection
コンストラクタを介してオブジェクトを渡すことで依存関係を注入します。
class Car constructor(private var engine : Engine){
fun drive() {
engine.start()
}
}
class Engine {
fun start() {
...
}
}
この例をHiltを使ってDIを行うと以下のようになります。
class Car @Inject constructor(private var engine : Engine){
fun drive() {
engine.start()
}
}
class Engine {
fun start() {
...
}
}
Field Injection
先述した例はField Injectionにあたります。
class Car {
var engine : Engine = Engine()
fun drive() {
engine.start()
}
}
class Engine {
fun start() {
...
}
}
Hiltを使って記述すると以下のようになります。
class Car {
@Inject lateinit var engine : Engine
fun drive() {
engine.start()
}
}
class Engine {
fun start() {
...
}
}
それではHiltを使って行きましょう。
Gradleの設定
GradleにHiltをビルドを含めるように設定します。
アプリのbuild.gradleファイルに以下を追加します。
...
apply plugin: 'kotlin-kapt'
apply plugin: 'dagger.hilt.android.plugin'
android {
...
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
}
dependencies {
...
implementation "com.google.dagger:hilt-android:2.28-alpha"
kapt "com.google.dagger:hilt-android-compiler:2.28-alpha"
}
次にプロジェクトのbuild.gradleに以下を追加します。
buildscript {
...
ext.hilt_version = '2.28-alpha'
dependencies {
...
classpath "com.google.dagger:hilt-android-gradle-plugin:$hilt_version"
}
}
ApplicationクラスにHiltを導入
Applicationクラスに@HiltAndroidApp
アノテーションを追加します。
Applicationクラスは自分で追加する必要があります。
@HiltAndroidApp
class MyApplication : Application() {
...
}
アプリのアプリケーションクラスがこれにより変更されるのでAndroid Manifesstファイルにそのことを記述する必要があります。
android:name=".MyApplication"
これによりHiltのコード自動生成が有効になります。
アクティビティに具象クラスをDIする
CarクラスをMainActivityアクティビティクラスにDIする場合を考えます。
まずHiltモジュールを作成します。HiltモジュールはDIするクラスの
- 生成方法
- ライフタイム
を管理します。
プロジェクトに新たにdiパッケージを追加してください。
diパッケージにCarModule.ktを追加し、以下のように記述するとアプリケーションの寿命を持ったEngineを生成するHiltモジュールになります。
package com.example.android.xxxxx.di
...
@InstallIn(SingletonComponent::class)
@Module
object CarModule {
@Provides
fun provideCar() : Car {
return Car()
}
}
@Module
はクラスがHiltモジュールで有ることを指定します。
@InstallIn(SingletonComponent::class)
はインスタンスの寿命を指定しています。
SingletonComponent::class
はアプリケーション全体が寿命であることを意味しています。
@InstallIn()
の引数を変えることで寿命を細かく変えることができます。
Component | Injector for |
---|---|
SingletonComponent | Application |
ActivityRetainedComponent | ViewModel (see View model extension) |
ActivityComponent | Activity |
FragmentComponent | Fragment |
ViewComponent | View |
ViewWithFragmentComponent | View with @WithFragmentBindings |
ServiceComponent | Service |
@Provides
はインスタンスを生成するメソッドにつけます。メソッド内でインスタンスの生成方法を指定します。
それでは、MainActivity
にCar
クラスを注入します。
@AndroidEntryPoint
class MainActivity : AppCompatActivity()
{
@Inject lateinit var car : Car
...
}
AndroidEntryPoint
は依存注入をする対象のクラスを指定します。
- Activity
- Fragment
- View
- Service
- BroadcastReceiver1
@Inject
は依存注入するメンバをしていします。Hiltは型を頼りにModule内の@Provides
で修飾されたメソッドを呼び出し、Carクラスをインスタンス化します。
ちなみに、上のように書くと依存注入が行われるたびに新しいインスタンスが生成されます。同じインスタンスを使いまわしたい場合は@Singleton
アノテーションをHiltモジュールに指定します。
package com.example.android.xxxxx.di
@InstallIn(SingletonComponent::class)
@Module
object CarModule {
@Provides
@Singleton
fun provideCar() : Car {
return Car()
}
}
上記の方法はField Injectionで依存注入していますが、以下のようにConstructor Injectionで実装することもできます。
@AndroidEntryPoint
class MainActivity @Inject constructor(var car : Car) : AppCompatActivity()
{
...
}
アクティビティにインターフェースをDIする
以下のようなEngineインターフェースとGasolineEngineクラスとElectricEngineクラスを想定します。
interface Engine
{
fun start()
fun stop()
}
class GasolineEngine : Engine
{
override fun start()
{
...
}
override fun stop()
{
...
}
}
class ElectricEngine : Engine
{
override fun start()
{
...
}
override fun stop()
{
...
}
}
UMLのクラス図で上記を表すと以下のようになります。
インターフェースにはabstractクラスのHiltモジュールを用意します。
@Qualifier
annotation class GasolineEngine
@Qualifier
annotation class ElectricEngine
@InstallIn(SigletonComponent::class)
@Module
abstract class EngineModule {
@GasolineEngine
@Binds
abstract fun bindGasolineEngine(impl : GasolineEngine) : Engine
@ElectriceEngine
@Binds
abstract fun bindElectricEngine(impl : ElectricEngine) : Engine
}
@Qualifier
はどのクラスをインスタンス化するかを指定します。
@Binds
はインスタンス化したい型を引数とし、返り値をインターフェースとしたabstractメソッドに付与します。
上記のモジュールは以下のようにMainActivityにDIします。
@AndroidEntryPoint
class MainActivity constructor() : AppCompatActivity()
{
@GasolineEngine
@Inject lateinit var main_engine : Engine
@ElectricEngine
@Inject lateinit var sub_engine : Engine
...
}