#43 AndroidでHiltを使ってみる
Android用のDI(Dependency Injection)ライブラリであるHiltの使い方を説明してみます。
公式ドキュメントはこちらです。
DIとは
ある程度の規模のプロジェクトになると、 「あるクラスの実装を行う際、他クラスのオブジェクトが必要」という場面に遭遇することが多くなります。 この関係を依存関係と呼びます。
もうすこし具体的な例で見てみましょう。ToDoデータを管理するTodoRepositoryImpl
は、
サーバーとの通信にOkHttpClient
を必要とするものとします。TodoRepositoryImpl
内で直接OkHttpClient
オブジェクトを作ってもいいのですが、ここでは次のようにコンストラクタの引数として受け取ることにします。
class TodoRepositoryImpl(private val client: OkHttpClient) {
...
}
そして、TodoRepositoryImpl
を使う場面で、次のようにOkHttpClient
を渡してオブジェクトを作ります。これが依存性注入(DI)と呼ばれる手法です。
val todoRepo = TodoRepositoryImpl(OkHttpClient())
依存するオブジェクトを差し込むには、次の2つの方法があります。
- コンストラクタの引数で渡す方法
- setterを使って渡す方法
本記事のゴール
本記事では、Hiltを使ってViewModelに「ToDoRepository
インターフェースを実装するオブジェクト」を差し込むことをゴールにします。
Hiltを追加する
※本節は公式ドキュメントほぼそのまんまの内容となります。
まず、プロジェクトのルートにあるbuild.gradleに、hilt-android-gradle-pluginを追加します。
buildscript {
...
dependencies {
...
classpath 'com.google.dagger:hilt-android-gradle-plugin:2.28-alpha'
}
}
次に、app/build.gradleにkaptとhiltプラグインを追加します。
apply plugin: 'kotlin-kapt'
apply plugin: 'dagger.hilt.android.plugin'
app/build.gradleのdependenciesにライブラリを追加します。
dependencies {
// by viewModels()を使いたい
implementation 'androidx.fragment:fragment-ktx:1.3.0-alpha06'
implementation "com.google.dagger:hilt-android:2.28-alpha"
kapt "com.google.dagger:hilt-android-compiler:2.28-alpha"
// ViewModelでHiltを使うために追加
implementation 'androidx.hilt:hilt-lifecycle-viewmodel:1.0.0-alpha01'
kapt 'androidx.hilt:hilt-compiler:1.0.0-alpha01'
}
Hiltや、by viewModels()
はJava
8の機能を使っているので、そのための設定も追加します。
android {
...
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
}
アプリケーションクラスを追加する
@HiltAndroidApp
のついたアプリケーションクラスを追加します。中身は空っぽでOKです。
@HiltAndroidApp
class MyApplication : Application() { ... }
アプリケーションクラスはAndroidManifest.xmlで使うよう指定しましょう。
<?xml version="1.0" encoding="utf-8"?>
<manifest
xmlns:android="http://schemas.android.com/apk/res/android"
package="com.mokelab.demo.hilt">
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme"
android:name=".MyApplication">
...
</application>
</manifest>
@AndroidEntryPointをつける
アクティビティとフラグメントに対し、@AndroidEntryPoint
のアノテーションをつけます。
@AndroidEntryPoint
class MainActivity : AppCompatActivity() { ... }
フラグメントにだけアノテーションを付け、親となるアクティビティにアノテーションを付けなかった場合、実行時に次のような例外が発生します。
java.lang.RuntimeException: Unable to start activity ComponentInfo{<コンポーネント名>}:
java.lang.IllegalStateException: Hilt Fragments must be attached to an @AndroidEntryPoint Activity.
Found: class com.mokelab.demo.hilt.MainActivity
ViewModelをフラグメントに持たせる
ここは、フラグメントに@AndroidEntryPoint
がついていること以外は通常のViewModelと同じです。
@AndroidEntryPoint
class MainFragment : Fragment(R.layout.main_fragment) {
...
private val vm: MainViewModel by viewModels()
...
}
ViewModelのコンストラクタをHilt対応版にする
@ViewModelInject constructor
にします。このアノテーションはandroidx.hilt:hilt-lifecycle-viewmodel
で提供されています。
class MainViewModel @ViewModelInject constructor(
private val repo: ToDoRepository
) : ViewModel() {
Hiltのモジュールを追加する
この時点でビルドしようとすると、次のようなビルドエラーが発生します。
エラー: [Dagger/MissingBinding] com.mokelab.demo.hilt.model.ToDoRepository cannot be provided
without an @Provides-annotated method.
public abstract static class ApplicationC implements HiltApplication_GeneratedInjector,
「MainViewModel
はToDoRepository
を必要としているけど、インターフェースなのでどうやってインスタンス作ればいいかわからないゾ」
というエラーになります。
ここで登場するのがHiltのモジュールです。先にコードを見てみましょう。
@Module
@InstallIn(ApplicationComponent::class)
object MainModule {
@Provides
@Singleton
fun provideOkHttpClient() = OkHttpClient()
@Provides
@Singleton
fun provideToDoRepository(client: OkHttpClient): ToDoRepository {
return ToDoRepositoryImpl(client)
}
}
@Module
と@InstallIn()
を付けます。各メソッドでは、どの型のオブジェクトが要求されたら、どれを返すかを実装します。
各メソッドには@Provides
を付け、
オブジェクトを1つだけ作って欲しい場合は@Singleton
も付けます。もしオブジェクトを作る際、他のオブジェクトが必要となる場合は
メソッドの引数にします。
以上でHiltを使ったDIは完了です。ビルドし、動作確認をしてみましょう。
まとめ
Hiltを使った実践的なDIのやり方を説明しました。本記事執筆時ではアルファ版ですが、素のDaggerよりもだいぶ使いやすくなっているので、 導入してみましょう。