50
40

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Swift/Kotlin愛好会Advent Calendar 2020

Day 21

DIライブラリ「Hilt」のセットアップ&使い方(Kotlin)

Last updated at Posted at 2021-01-23

はじめに

本記事は Swift/Kotlin愛好会 Advent Calendar 2020 の21日目の記事です。
空いているので埋めました。

Hiltのセットアップ方法と使い方を紹介します。

「Hilt」とは?

Android向けのDIライブラリです。

Daggerの上に構築されており、DaggerコンポーネントやAndroidクラスを自動的に注入するコードを生成するため、Daggerを使うよりボイラープレートが減ります。

環境

  • OS:macOS Big Sur 11.1
  • Android Studio:4.1.2
  • Kotlin:1.4.10
  • Gradle:6.8
  • Gradle plugin:4.1.2
  • Hilt:2.31-alpha

セットアップ

kaptのセットアップ

Hiltはアノテーションを使ってコードを生成するため、kaptが必要です。
以下の記事を参考にセットアップしてください。
https://qiita.com/uhooi/items/836902cdd322f9accded

Hiltのインストール

ルート直下の「build.gradle」にプラグインを追加します。

/biuld.gradle
buildscript {
    ext {
+         hilt_version = '2.31-alpha'
    }
    repositories {
        google()
        jcenter()
    }
    dependencies {
+         classpath "com.google.dagger:hilt-android-gradle-plugin:$hilt_version"
    }
}

appフォルダ配下の「build.gradle」にプラグインの適用と依存関係を追加します。

/app/build.gradle
+ apply plugin: 'dagger.hilt.android.plugin'

dependencies {
+     implementation "com.google.dagger:hilt-android:$rootProject.hilt_version"
+     kapt "com.google.dagger:hilt-android-compiler:$rootProject.hilt_version"
}

以下はなくても動作しましたが、公式ドキュメントに記載されているので追加しています。
詳しい方がいたら教えていただけると嬉しいです :pray:

/app/build.gradle
android {
+     compileOptions {
+         sourceCompatibility JavaVersion.VERSION_1_8
+         targetCompatibility JavaVersion.VERSION_1_8
+     }
}

+ kapt {
+     correctErrorTypes = true
+ }

アプリケーションクラスの実装

アプリケーションクラスに @HiltAndroidApp アノテーションを付けます。
これにより、アプリケーションレベルのコンポーネントがHiltで使えるようになります。

要は「このアプリではHiltを使いますよー」というおまじないです。

UhooiPicBookApp.kt
+ import dagger.hilt.android.HiltAndroidApp

+ @HiltAndroidApp
class UhooiPicBookApp : Application() {
    // ...
}

これでHiltのセットアップは完了です。

使い方

①インターフェースを通してコンストラクタインジェクションする

Before

例として、以下のインターフェースとその実装クラスを用意します。

MonstersRepository.kt
interface MonstersRepository {
    // ...
}
MonstersFirestoreClient.kt
class MonstersFirestoreClient : MonstersRepository {
    // ...
}

上記のインターフェースを通して、以下のビューモデルにDIする方法を紹介します。
現状ではビューモデル内で実装クラスのインスタンスを生成しているため、DIできていません。

MonsterListViewModel.kt
class MonsterListViewModel : ViewModel() {
    private val repository: MonstersRepository = MonstersFirestoreClient() // TODO: DIする
}

After

ビューモデルのコンストラクタに @Inject アノテーションを付け、コンストラクタでインターフェースを型とした引数を用意します。
これにより、Hiltが自動的に対象インターフェースの実装クラスをDIします。

MonsterListViewModel.kt
+ import javax.inject.Inject

- class MonsterListViewModel : ViewModel() {
+ class MonsterListViewModel @Inject constructor(
+     private val repository: MonstersRepository
+ ) : ViewModel() {
-         private val repository: MonstersRepository = MonstersFirestoreClient() // TODO: DIする
}

実装クラスのコンストラクタにも @Inject アノテーションを付ける必要があります。

MonstersFirestoreClient.kt
+ import javax.inject.Inject

- class MonstersFirestoreClient : MonstersRepository {
+ class MonstersFirestoreClient @Inject constructor() : MonstersRepository {
    // ...
}

しかし、インターフェースの実装クラスは複数あるかもしれないので、これだけではHiltがどの実装クラスをDIしていいかわかりません。

そこで用意するのがHiltモジュールです。
Hiltモジュール内で「このインターフェースにはこの実装クラスをDIする」ことを定義し、Hiltにインスタンスの提供方法を伝えます。

Hiltモジュールを新規作成します。

AppModule.kt
package com.theuhooi.uhooipicbook.di

import com.theuhooi.uhooipicbook.modules.monsterlist.MonstersRepository
import com.theuhooi.uhooipicbook.repository.monsters.firebase.MonstersFirestoreClient
import dagger.Binds
import dagger.Module
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import javax.inject.Singleton

@Module
@InstallIn(SingletonComponent::class)
abstract class AppModule {
    @Singleton
    @Binds
    abstract fun bindMonstersRepository(monstersFirestoreClient: MonstersFirestoreClient): MonstersRepository
}

コードを上から説明します。

Hiltモジュールであることを表すために @Module アノテーションを付けます。

Hiltモジュールの依存関係をどこまで適用するかの範囲を表すため、 @InstallIn アノテーションを付けます。
SingletonComponent は、Hiltモジュールの全依存関係がアプリの全アクティビティで使えることを表します。
アプリ全体で適用されるHiltモジュールは、ルート直下に di パッケージを作成し、その中に AppModule.kt という名前で実装するのが一般的なようです。

インターフェースにDIする場合は、Hiltモジュールを抽象クラスで定義します。
インスタンスの提供方法を伝える関数も抽象関数とし、 @Binds アノテーションを付けます。
すべての依存関係で1つのインスタンスを使い回すには @Singleton アノテーションを付けます。

関数は bind{インターフェース名}({実装クラス名の先頭を小文字にしたもの}: {実装クラス名}): {インターフェース名} と定義するのが一般的なようです。
試していないですが、インターフェースと実装クラスさえ指定していれば、関数名と引数名は任意だと思います。

これで「 MonstersRepository インターフェースに MonstersFirestoreClient クラスのインスタンスをDIする」ことをHiltに伝えられたので、DIに必要な実装が完了です。

②自分で所有していないクラスをDIする

まだ実装したことがないため、簡単にのみ説明します。

インターフェースのときとは異なり、Hiltモジュールを object で定義し、 インスタンスの提供方法を伝える関数は通常の関数に @Provides アノテーションを付けます。
関数内でインスタンスを生成して返します。

AppModule.kt
@Module
@InstallIn(SingletonComponent::class)
object AppModule {
    @Probides
    fun provideGoogleRepository(): GoogleRepository {
        return GoogleDBClient()
    }

③ビューモデルをフィールドインジェクションする

Before

例として、以下のフラグメントに先ほど紹介した MonsterListViewModel をフィールドインジェクションする方法を紹介します。

MonsterListFragment.kt
class MonsterListFragment : Fragment() {
    private val viewModel: MonsterListViewModel // TODO: DIする
}
MonsterListViewModel.kt
import javax.inject.Inject

class MonsterListViewModel @Inject constructor(
    private val repository: MonstersRepository
) : ViewModel() {
    // ...
}

After

ビューモデルに @HiltViewModel アノテーションを付けます。

MonsterListViewModel.kt
+ import dagger.hilt.android.lifecycle.HiltViewModel
import javax.inject.Inject

+ @HiltViewModel
class MonsterListViewModel @Inject constructor(
    private val repository: MonstersRepository
) : ViewModel() {
    // ...
}

フラグメントに @AndroidEntryPoint アノテーションを付け、ビューモデルの変数に by viewModels() を付ければ、自動的にDIされます。

MonsterListFragment.kt
+ import androidx.fragment.app.viewModels
+ import dagger.hilt.android.AndroidEntryPoint

+ @AndroidEntryPoint
class MonsterListFragment : Fragment() {
-     private val viewModel: MonsterListViewModel // TODO: DIする
+     private val viewModel: MonsterListViewModel by viewModels()
}

@AndroidEntryPoint アノテーションは依存するクラスにも付ける必要があります。
例えば、フラグメントをにアノテーションを付ける場合は、そのフラグメントを使っているアクティビティ(今回は MainActivity とする)にもアノテーションを付ける必要があるということです。

MainActivity.kt
+ import dagger.hilt.android.AndroidEntryPoint

+ @AndroidEntryPoint
class MainActivity : AppCompatActivity() {
    // ...
}

viewModels() は対象のクラス(今回は MonsterListFragment )のみで生存するようです。
MonsterListFragment を使用しているアクティビティ( MainActivity )でも生存したい場合は activityViewModels() とすればいいようです。

おまけ: チートシート

公式でHiltのチートシートが公開されています。
一通り目を通してから、公式ドキュメントでキャッチアップするのがいいかもしれません。

おわりに

少ないコード量でDIを実現できました!
Hiltにはできることがまだまだたくさんあるので、必要に応じて勉強していきたいです。

以上、 Swift/Kotlin愛好会 Advent Calendar 2020 の21日目の記事でした。

参考リンク

50
40
1

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
50
40

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?