30
17

More than 3 years have passed since last update.

Hiltの使い方

Last updated at Posted at 2020-09-22

この記事について

androidでHiltを使う方法をまとめます。

DI(依存注入)とは?

依存とはあるオブジェクトから他のオブジェクトを参照・使用することを指します。
以下の例ではCarクラスはEngineクラスに依存していることになります。

class Car {
    var engine : Engine = Engine()
    fun drive() {
        engine.start()
    }
}

class Engine {
    fun start() {
        ...
    }
}

UMLのクラス図とシーケン図では以下のように表されます。

uml.png

uml.png

DI(依存注入)とはあるオブジェクトから他のオブジェクトを参照・使用する関係(依存)を注入(あるオブジェクトと他のオブジェクトをつなげる)ことを指します。
Hiltでは以下の2種のDIをサポートしています。
1. Constructor Injection
2. 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するクラスの

  1. 生成方法
  2. ライフタイム

を管理します。

プロジェクトに新たに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はインスタンスを生成するメソッドにつけます。メソッド内でインスタンスの生成方法を指定します。
それでは、MainActivityCarクラスを注入します。

@AndroidEntryPoint
class MainActivity : AppCompatActivity()
{
    @Inject lateinit var car : Car
    ...
}

AndroidEntryPointは依存注入をする対象のクラスを指定します。

  1. Activity
  2. Fragment
  3. View
  4. Service
  5. 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のクラス図で上記を表すと以下のようになります。

uml.png

インターフェースには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
    ...

}

参考文献

30
17
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
30
17