KotlinでDaggerとHiltを使った依存性注入の完全ガイド

Kotlinで依存性注入(DI)を実装する際、DaggerとHiltは非常に強力なツールです。依存性注入は、コンポーネント同士の依存関係を効率的に管理し、コードの保守性やテストのしやすさを向上させる重要な技術です。

Daggerは、コンパイル時に依存関係を解析して注入するため、高速で安全な依存性注入が可能です。しかし、その設定や構成は初心者には少し複雑に感じることがあります。一方で、HiltはDaggerをベースにしており、Androidアプリ開発向けに簡易化されたDIライブラリです。Hiltを使えば、少ない設定で効率的に依存性注入が実現できます。

本記事では、DaggerとHiltを用いた依存性注入の基本から実践まで、Kotlinでの具体的な実装方法を解説します。DIを理解し、DaggerとHiltを適切に活用することで、堅牢で拡張性の高いAndroidアプリを構築できるようになります。

目次

依存性注入(DI)とは何か


依存性注入(Dependency Injection、DI)とは、クラスが必要とする依存関係(他のクラスやオブジェクト)を、外部から提供するデザインパターンのことです。これにより、クラスが自分自身で依存関係を生成する必要がなくなり、コードの柔軟性やテストのしやすさが向上します。

依存性注入の目的

  • 保守性の向上:依存関係を外部から注入することで、コードの変更が容易になります。
  • テストの容易さ:依存関係をモックやスタブに置き換えることで、ユニットテストがしやすくなります。
  • 再利用性の向上:クラスが特定の依存関係に強く依存しないため、再利用がしやすくなります。

依存性注入の例


例えば、以下のようなRepositoryクラスとServiceクラスがあるとします。

class Repository {
    fun getData(): String {
        return "Hello from Repository"
    }
}

class Service(private val repository: Repository) {
    fun fetchData(): String {
        return repository.getData()
    }
}

このServiceクラスはRepositoryクラスに依存しています。依存性注入を利用すると、Serviceクラスの依存関係を外部から注入することができます。

依存性注入のメリット

  1. 疎結合:クラス間の依存が減り、柔軟性が高まります。
  2. テスト可能:モックを使ったテストが容易になります。
  3. コードの可読性:依存関係が明示的になるため、コードが理解しやすくなります。

依存性注入は、DaggerやHiltのようなDIフレームワークを使うことで効率的に実現できます。次のセクションでは、Daggerの基本概念と特徴について解説します。

Daggerの基本概念と特徴

Daggerは、Googleが提供する依存性注入(DI)ライブラリで、主にAndroidアプリ開発で使われます。Daggerはコンパイル時に依存関係を解析・生成するため、高速かつ安全なDIが可能です。

Daggerの基本概念

  • Component:依存関係を提供するインターフェース。@Componentアノテーションを使って定義します。
  • Module:依存関係を生成するクラス。@Module@Providesアノテーションを使って提供するオブジェクトを定義します。
  • Inject:依存関係の注入ポイントに使用するアノテーション。フィールドやコンストラクタに適用します。

基本的なDaggerの構成例

// Module: 依存関係を提供する
@Module
class RepositoryModule {
    @Provides
    fun provideRepository(): Repository {
        return Repository()
    }
}

// Component: 依存関係を注入する
@Component(modules = [RepositoryModule::class])
interface AppComponent {
    fun inject(activity: MainActivity)
}

// クラスへの依存関係注入
class MainActivity {
    @Inject
    lateinit var repository: Repository

    fun onCreate() {
        DaggerAppComponent.create().inject(this)
        println(repository.getData())
    }
}

Daggerの特徴

  1. コンパイル時の依存関係解析
    Daggerはコンパイル時に依存関係を解析し、エラーがあれば早期に検出します。これにより、ランタイムエラーのリスクが減ります。
  2. パフォーマンスが高い
    依存関係をコンパイル時に生成するため、ランタイムでのパフォーマンスに優れています。
  3. スコープの管理
    Daggerでは@Singletonやカスタムスコープを使用して、依存関係のライフサイクルを管理できます。
  4. 拡張性
    大規模なアプリケーションでも複数のモジュールやコンポーネントを組み合わせて柔軟に設計できます。

Daggerは強力なDIツールですが、設定が複雑になることがあります。次のセクションでは、Daggerのセットアップ手順について詳しく解説します。

Daggerのセットアップ手順

KotlinプロジェクトにDaggerを導入する手順をステップバイステップで解説します。Daggerを利用することで、依存関係の管理が効率化され、コードの保守性が向上します。

1. 依存関係の追加

まず、build.gradle.ktsファイルにDaggerの依存関係を追加します。

dependencies {
    implementation("com.google.dagger:dagger:2.x")          // Daggerのメインライブラリ
    kapt("com.google.dagger:dagger-compiler:2.x")           // コンパイル時アノテーション処理
}

注意2.xの部分はDaggerの最新バージョンに置き換えてください。

さらに、kotlin-kaptプラグインを適用します。

plugins {
    id("kotlin-kapt")
}

2. Daggerのコンポーネントとモジュールの作成

依存関係を提供するための@Moduleと依存関係を注入する@Componentを作成します。

Repositoryクラスの例

class Repository {
    fun getData(): String = "Hello from Repository"
}

Moduleの作成

import dagger.Module
import dagger.Provides

@Module
class RepositoryModule {
    @Provides
    fun provideRepository(): Repository {
        return Repository()
    }
}

Componentの作成

import dagger.Component

@Component(modules = [RepositoryModule::class])
interface AppComponent {
    fun inject(activity: MainActivity)
}

3. 依存関係の注入

MainActivityに依存関係を注入します。

import javax.inject.Inject

class MainActivity {
    @Inject
    lateinit var repository: Repository

    fun onCreate() {
        DaggerAppComponent.create().inject(this)
        println(repository.getData())
    }
}

4. ビルドと確認

プロジェクトをビルドし、MainActivityを実行して、依存関係が正しく注入されているか確認します。出力が以下のようになれば成功です。

Hello from Repository

よくあるエラーと対処法

  1. エラーDaggerAppComponentが見つからない。
    対処法:ビルドを再実行して、kaptによるコード生成が行われるようにします。
  2. エラー@Injectされたフィールドがnullになる。
    対処法inject()メソッドが正しく呼び出されていることを確認します。

これでDaggerのセットアップは完了です。次のセクションでは、Hiltの概要と特徴について解説します。

Hiltの概要と特徴

Hiltは、DaggerをベースにGoogleが提供するAndroid向けの依存性注入(DI)ライブラリです。Daggerを簡単に使えるように抽象化しており、少ない設定で依存性注入を実現できます。HiltはAndroidアプリのライフサイクルを考慮した設計になっており、ActivityやFragmentなどでの依存性注入が簡単に行えます。

Hiltの主な特徴

  1. シンプルな設定
    HiltはDaggerの複雑な設定を簡素化し、少ないコードで依存性注入を実現できます。
  2. Androidライフサイクル対応
    HiltはActivity、Fragment、ViewModelなどのAndroidコンポーネントのライフサイクルをサポートしています。
  3. コード生成の自動化
    Hiltはアノテーションを利用して依存関係を自動で生成するため、手動でのコンポーネント管理が不要です。
  4. テストサポート
    テスト用の依存関係を簡単に提供するため、ユニットテストやUIテストが容易になります。

Hiltの基本コンポーネント

  • @HiltAndroidApp
    アプリケーションクラスに付けるアノテーションで、HiltのDIコンテナを生成します。
  • @Inject
    依存関係を注入するためのアノテーションです。
  • @Module@InstallIn
    依存関係を提供するためのモジュールと、そのモジュールを適用するAndroidコンポーネントを指定します。
  • @EntryPoint
    Hilt外のクラスで依存関係を注入したい場合に使用します。

Hiltの利便性

Hiltは、Daggerを直接使う場合と比べて次の点で利便性があります。

  • ボイラープレートの削減:Daggerで必要なComponentの定義や初期化が不要です。
  • 簡単な統合:ActivityやFragmentで自動的にDIが利用できます。
  • 標準化:Googleが推奨するAndroid開発の標準的なDIソリューションです。

Hiltのアーキテクチャ

Hiltでは、以下のライフサイクルに合わせたスコープを提供します。

  • Applicationスコープ:アプリ全体で共有される依存関係。
  • Activityスコープ:Activityごとに共有される依存関係。
  • Fragmentスコープ:Fragmentごとに共有される依存関係。
  • ViewModelスコープ:ViewModelごとに共有される依存関係。

Hiltを使用することで、DIを簡単に実装し、保守性やテスト性に優れたAndroidアプリを構築できます。次のセクションでは、Hiltのセットアップ方法について解説します。

Hiltのセットアップ方法

KotlinプロジェクトにHiltを導入するためのセットアップ手順を解説します。Hiltを使用すると、Daggerの煩雑な設定を簡略化し、Androidアプリで効率的に依存性注入(DI)が行えます。

1. Hiltの依存関係を追加

まず、build.gradle.ktsファイルにHiltの依存関係を追加します。

plugins {
    id("kotlin-kapt")
    id("dagger.hilt.android.plugin")
}

dependencies {
    implementation("com.google.dagger:hilt-android:2.x")    // Hiltライブラリ
    kapt("com.google.dagger:hilt-android-compiler:2.x")     // Hiltコンパイラ
}

注意2.xの部分はHiltの最新バージョンに置き換えてください。

さらに、プロジェクトレベルのbuild.gradle.ktsにHiltプラグインを追加します。

buildscript {
    dependencies {
        classpath("com.google.dagger:hilt-android-gradle-plugin:2.x")
    }
}

2. アプリケーションクラスの作成

@HiltAndroidAppアノテーションを付けたアプリケーションクラスを作成します。

import android.app.Application
import dagger.hilt.android.HiltAndroidApp

@HiltAndroidApp
class MyApplication : Application()

AndroidManifest.xmlにこのアプリケーションクラスを登録します。

<application
    android:name=".MyApplication"
    ... >
</application>

3. Activityへの依存関係注入

Hiltを使ってActivityに依存関係を注入します。

import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import dagger.hilt.android.AndroidEntryPoint
import javax.inject.Inject

@AndroidEntryPoint
class MainActivity : AppCompatActivity() {

    @Inject
    lateinit var repository: Repository

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        println(repository.getData())
    }
}

4. モジュールの作成

依存関係を提供するためのモジュールを作成します。

import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import javax.inject.Singleton

@Module
@InstallIn(SingletonComponent::class)
object RepositoryModule {

    @Provides
    @Singleton
    fun provideRepository(): Repository {
        return Repository()
    }
}

5. ビルドと確認

プロジェクトをビルドし、MainActivityを実行します。以下のような出力が確認できれば、Hiltによる依存性注入は成功です。

Hello from Repository

よくあるエラーと対処法

  1. エラー@HiltAndroidAppが付いたクラスが見つからない。
    対処法:アプリケーションクラスに@HiltAndroidAppアノテーションが正しく付いているか確認し、ビルドを再実行します。
  2. エラーHiltAndroidAppの初期化エラー。
    対処法AndroidManifest.xmlで正しいアプリケーションクラスが設定されていることを確認します。

これでHiltのセットアップは完了です。次のセクションでは、DaggerとHiltを用いた具体的な実装例を解説します。

DaggerとHiltの実装例

ここでは、DaggerとHiltを用いた依存性注入(DI)の具体的な実装方法をKotlinコードで解説します。それぞれのDIフレームワークを使用して、RepositoryViewModelを注入するサンプルを見ていきます。


Daggerを使った依存性注入の実装例

  1. Repositoryクラスの作成
class Repository {
    fun getData(): String = "Hello from Dagger Repository"
}
  1. Moduleの作成

依存関係を提供するRepositoryModuleを作成します。

import dagger.Module
import dagger.Provides
import javax.inject.Singleton

@Module
class RepositoryModule {
    @Provides
    @Singleton
    fun provideRepository(): Repository {
        return Repository()
    }
}
  1. Componentの作成

AppComponentを作成し、依存関係を注入します。

import dagger.Component
import javax.inject.Singleton

@Singleton
@Component(modules = [RepositoryModule::class])
interface AppComponent {
    fun inject(activity: MainActivity)
}
  1. Activityへの依存性注入
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import javax.inject.Inject

class MainActivity : AppCompatActivity() {

    @Inject
    lateinit var repository: Repository

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        DaggerAppComponent.create().inject(this)
        println(repository.getData())
    }
}

Hiltを使った依存性注入の実装例

  1. Repositoryクラスの作成
class Repository {
    fun getData(): String = "Hello from Hilt Repository"
}
  1. Moduleの作成

Hiltで依存関係を提供するRepositoryModuleを作成します。

import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import javax.inject.Singleton

@Module
@InstallIn(SingletonComponent::class)
object RepositoryModule {
    @Provides
    @Singleton
    fun provideRepository(): Repository {
        return Repository()
    }
}
  1. アプリケーションクラスの作成
import android.app.Application
import dagger.hilt.android.HiltAndroidApp

@HiltAndroidApp
class MyApplication : Application()
  1. Activityへの依存性注入
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import dagger.hilt.android.AndroidEntryPoint
import javax.inject.Inject

@AndroidEntryPoint
class MainActivity : AppCompatActivity() {

    @Inject
    lateinit var repository: Repository

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        println(repository.getData())
    }
}

ViewModelへの依存性注入

Hiltを使ってViewModelにも依存性を注入できます。

  1. ViewModelの作成
import androidx.lifecycle.ViewModel
import dagger.hilt.android.lifecycle.HiltViewModel
import javax.inject.Inject

@HiltViewModel
class MainViewModel @Inject constructor(
    private val repository: Repository
) : ViewModel() {
    fun getRepositoryData(): String = repository.getData()
}
  1. ActivityでViewModelを利用
import android.os.Bundle
import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity
import dagger.hilt.android.AndroidEntryPoint

@AndroidEntryPoint
class MainActivity : AppCompatActivity() {

    private val viewModel: MainViewModel by viewModels()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        println(viewModel.getRepositoryData())
    }
}

出力結果

アプリを実行すると、以下の出力が表示されます。

Hello from Dagger Repository

または

Hello from Hilt Repository

これでDaggerとHiltを使った依存性注入の具体的な実装が理解できました。次のセクションでは、テストでの依存性注入の活用法について解説します。

テストでの依存性注入活用法

依存性注入(DI)を用いることで、ユニットテストやUIテストが容易になります。DaggerやHiltを利用することで、モックやスタブを使ってテストを効率的に実施できます。ここでは、Hiltを用いたテストの手法を解説します。


Hiltを使ったユニットテスト

Hiltはテスト用の依存関係を簡単に提供できるため、テストクラスでもDIが利用可能です。

1. テスト用の依存関係を用意

Hiltのモジュールでテスト用のモック依存関係を提供します。

import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import javax.inject.Singleton

@Module
@InstallIn(SingletonComponent::class)
object TestRepositoryModule {

    @Provides
    @Singleton
    fun provideRepository(): Repository {
        return object : Repository {
            override fun getData(): String = "Hello from Test Repository"
        }
    }
}

2. テストクラスの作成

Hiltを使用したテストクラスには@HiltAndroidTestアノテーションを付けます。

import androidx.test.ext.junit.runners.AndroidJUnit4
import dagger.hilt.android.testing.HiltAndroidTest
import dagger.hilt.android.testing.HiltTestApplication
import org.junit.Test
import org.junit.runner.RunWith
import javax.inject.Inject
import org.junit.Assert.assertEquals

@HiltAndroidTest
@RunWith(AndroidJUnit4::class)
class MainActivityTest {

    @Inject
    lateinit var repository: Repository

    @Test
    fun testRepositoryData() {
        assertEquals("Hello from Test Repository", repository.getData())
    }
}

3. テストのセットアップ

AndroidManifest.xmlにテスト用のHiltTestApplicationを指定します。

<application
    android:name="dagger.hilt.android.testing.HiltTestApplication">
</application>

ViewModelのテスト

Hiltを用いたViewModelのテスト手順を示します。

1. ViewModelの作成

import androidx.lifecycle.ViewModel
import dagger.hilt.android.lifecycle.HiltViewModel
import javax.inject.Inject

@HiltViewModel
class MainViewModel @Inject constructor(
    private val repository: Repository
) : ViewModel() {
    fun getRepositoryData(): String = repository.getData()
}

2. テストクラスの作成

import androidx.arch.core.executor.testing.InstantTaskExecutorRule
import dagger.hilt.android.testing.HiltAndroidTest
import dagger.hilt.android.testing.HiltTestApplication
import org.junit.Assert.assertEquals
import org.junit.Rule
import org.junit.Test
import javax.inject.Inject

@HiltAndroidTest
class MainViewModelTest {

    @get:Rule
    var instantTaskExecutorRule = InstantTaskExecutorRule()

    @Inject
    lateinit var viewModel: MainViewModel

    @Test
    fun testViewModelRepositoryData() {
        val data = viewModel.getRepositoryData()
        assertEquals("Hello from Test Repository", data)
    }
}

Hiltを使ったUIテスト

HiltはUIテストにも対応しています。例えば、Espressoを用いたテストが可能です。

1. UIテストクラス

import androidx.test.ext.junit.rules.ActivityScenarioRule
import androidx.test.ext.junit.runners.AndroidJUnit4
import dagger.hilt.android.testing.HiltAndroidTest
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith

@HiltAndroidTest
@RunWith(AndroidJUnit4::class)
class MainActivityUITest {

    @get:Rule
    val activityRule = ActivityScenarioRule(MainActivity::class.java)

    @Test
    fun checkRepositoryDataDisplayed() {
        // Espressoを使ったUIテスト
        onView(withId(R.id.textView)).check(matches(withText("Hello from Test Repository")))
    }
}

テスト時の依存関係の差し替え

Hiltを使うと、テスト環境で簡単に依存関係を差し替えることができます。@InstallInアノテーションを使って、テスト用のモジュールを作成し、テストでのみモックを提供するように設定できます。


まとめ

Hiltを使った依存性注入により、モックやスタブを活用したテストが容易になります。これにより、ユニットテストやUIテストの品質が向上し、堅牢なアプリケーションを開発できます。次のセクションでは、DaggerやHiltでよくあるエラーとその解決方法を解説します。

よくあるエラーとその解決方法

DaggerやHiltを使用して依存性注入(DI)を実装する際、設定ミスやアノテーションの使い方でエラーが発生することがあります。ここでは、よくあるエラーとその解決方法について解説します。


1. コンポーネントが見つからないエラー

エラーメッセージ例

error: cannot find symbol class DaggerAppComponent

原因
Daggerによるコード生成がうまく行われていない場合に発生します。

解決方法

  • ビルドの再実行Build > Rebuild Projectを実行して、Daggerのコード生成が正しく行われるか確認します。
  • kaptの設定確認build.gradle.ktskotlin-kaptが含まれていることを確認します。
plugins {
    id("kotlin-kapt")
}

2. Hiltエントリポイントの初期化エラー

エラーメッセージ例

Hilt does not have entry point for class: MyApplication

原因
アプリケーションクラスに@HiltAndroidAppアノテーションが付いていない場合に発生します。

解決方法
アプリケーションクラスに@HiltAndroidAppを追加します。

@HiltAndroidApp
class MyApplication : Application()

3. 依存関係が解決できないエラー

エラーメッセージ例

error: [Dagger/MissingBinding] Repository cannot be provided without an @Provides-annotated method

原因
依存関係が提供されていない場合に発生します。

解決方法
モジュールで依存関係を提供する@Providesメソッドを定義します。

@Module
@InstallIn(SingletonComponent::class)
object RepositoryModule {
    @Provides
    fun provideRepository(): Repository {
        return Repository()
    }
}

4. 循環依存エラー

エラーメッセージ例

error: [Dagger/CyclicDependency] Found cyclic dependency

原因
クラス間に依存関係のループがある場合に発生します。

解決方法

  • 依存関係の設計を見直し、循環依存を解消する。
  • インターフェースを導入して依存関係を分離する。

5. スコープの不一致エラー

エラーメッセージ例

error: [Dagger/IncompatibleScopes] Singleton component cannot depend on Activity scoped component

原因
異なるスコープ間で依存関係を注入しようとした場合に発生します。

解決方法

  • スコープを揃える(例:@Singleton@ActivityScopedを混在させない)。
  • スコープの範囲に合った依存関係を提供する。

6. ViewModelでの依存性注入エラー

エラーメッセージ例

error: [Hilt] HiltViewModel annotation is missing or incorrect

原因
ViewModelに@HiltViewModelが付いていない、または正しいコンストラクタが定義されていない。

解決方法
ViewModelに@HiltViewModelアノテーションを付け、依存関係をコンストラクタで受け取ります。

@HiltViewModel
class MainViewModel @Inject constructor(
    private val repository: Repository
) : ViewModel()

7. アノテーションのミス

エラーメッセージ例

error: Annotation @Inject cannot be used on methods

原因
@Injectアノテーションが不適切な場所で使用されています。

解決方法
@Injectは、フィールドまたはコンストラクタにのみ使用します。

class Repository @Inject constructor()

まとめ

DaggerやHiltで発生するエラーは、設定やアノテーションのミスが原因であることが多いです。エラーメッセージをよく読み、依存関係の提供方法やスコープ設定を見直すことで、問題を解決できます。次のセクションでは、この記事の内容をまとめます。

まとめ

本記事では、Kotlinにおける依存性注入(DI)をDaggerとHiltを用いて実装する方法について解説しました。DIの基本概念から、DaggerとHiltの特徴、セットアップ手順、実装例、テストでの活用法、そしてよくあるエラーとその解決方法まで、幅広くカバーしました。

  • Daggerは、コンパイル時に依存関係を解析し、高速かつ安全なDIを提供します。
  • Hiltは、Daggerを簡略化したAndroid向けDIライブラリで、少ない設定で依存性注入が可能です。
  • テストでは、モックやスタブを使って効率的に依存関係を差し替え、ユニットテストやUIテストの品質を向上させることができます。
  • よくあるエラーは、依存関係の設定やスコープの不一致が原因で発生しますが、適切なアノテーションと設定を確認することで解決可能です。

DaggerとHiltを正しく活用することで、保守性、拡張性、テスト性に優れたAndroidアプリケーションを構築できます。これらのツールを使いこなし、効率的なKotlin開発を進めていきましょう。

コメント

コメントする

目次