Hilt を使用した依存関係挿入

Hilt は Android 用の依存関係インジェクション ライブラリです。これを使うことで、プロジェクトで依存関係の注入(DI)を手動で行うためのボイラープレートが減ります。手動で依存関係の注入を行うには、すべてのクラスとその依存関係を手作業で作成し、コンテナを使用して依存関係の再利用と管理を行う必要があります。

Hilt は、プロジェクト内のすべての Android クラスにコンテナを提供し、そのライフサイクルを自動で管理することで、アプリケーションで DI を行うための標準的な方法を提供します。Hilt は、よく知られた DI ライブラリである Dagger の上に構築されているため、コンパイル時の正確性、実行時のパフォーマンス、スケーラビリティ、Android Studio のサポートといった Dagger の恩恵を受けられます。詳細については、Hilt と Dagger をご覧ください。

このガイドでは、Hilt とそこで生成されるコンテナの基本概念について説明します。また、既存のアプリで Hilt を使用できるようにする方法も紹介します。

依存関係を追加する

まず、hilt-android-gradle-plugin プラグインをプロジェクトのルート build.gradle ファイルに追加します。

GroovyKotlin
plugins {
  ...
  id 'com.google.dagger.hilt.android' version '2.56.1' apply false
}
plugins {
  ...
  id("com.google.dagger.hilt.android") version "2.56.1" apply false
}

次に、Gradle プラグインを適用し、app/build.gradle ファイルに次の依存関係を追加します。

GroovyKotlin
...
plugins {
  id 'com.google.devtools.ksp'
  id 'com.google.dagger.hilt.android'
}

android {
  ...
}

dependencies {
  implementation "com.google.dagger:hilt-android:2.56.1"
  ksp "com.google.dagger:hilt-compiler:2.56.1"
}
plugins {
  id("com.google.devtools.ksp")
  id("com.google.dagger.hilt.android")
}

android {
  ...
}

dependencies {
  implementation("com.google.dagger:hilt-android:2.56.1")
  ksp("com.google.dagger:hilt-android-compiler:2.56.1")
}

Hilt は Java 8 の機能を使用しています。プロジェクトで Java 8 を有効にするには、app/build.gradle ファイルに次の内容を追加します。

GroovyKotlin
android {
  ...
  compileOptions {
    sourceCompatibility JavaVersion.VERSION_1_8
    targetCompatibility JavaVersion.VERSION_1_8
  }
}
android {
  ...
  compileOptions {
    sourceCompatibility = JavaVersion.VERSION_1_8
    targetCompatibility = JavaVersion.VERSION_1_8
  }
}

Hilt アプリケーション クラス

Hilt を使用するアプリには、@HiltAndroidApp アノテーションが付けられた Application クラスが含まれている必要があります。

@HiltAndroidApp は、Hilt のコード生成をトリガーします。これには、アプリケーション レベルの依存関係コンテナとして機能するアプリケーションの基本クラスも含まれます。

KotlinJava
@HiltAndroidApp
class ExampleApplication : Application() { ... }
@HiltAndroidApp
public class ExampleApplication extends Application { ... }

ここで生成された Hilt コンポーネントは、Application オブジェクトのライフサイクルにアタッチされ、依存関係を提供します。また、アプリの親コンポーネントであることから、他のコンポーネントがこのコンポーネントの提供する依存関係にアクセスできます。

Android クラスに依存関係を注入する

Application クラスで Hilt がセットアップされ、アプリケーション レベルのコンポーネントが利用可能になると、@AndroidEntryPoint アノテーションが付けられた他の Android クラスに依存関係を提供できるようになります。

KotlinJava
@AndroidEntryPoint
class ExampleActivity : AppCompatActivity() { ... }
@AndroidEntryPoint
public class ExampleActivity extends AppCompatActivity { ... }

現在、Hilt は以下の Android クラスをサポートしています。

  • Application@HiltAndroidApp を使用)
  • ViewModel@HiltViewModel を使用)
  • Activity
  • Fragment
  • View
  • Service
  • BroadcastReceiver

Android クラスに @AndroidEntryPoint アノテーションを付ける場合は、それに依存する Android クラスにもアノテーションを付ける必要があります。たとえば、フラグメントにアノテーションを付ける場合は、そのフラグメントを使用するアクティビティにもアノテーションを付ける必要があります。

@AndroidEntryPoint は、プロジェクト内の Android クラスごとに個別の Hilt コンポーネントを生成します。これらのコンポーネントは、コンポーネント階層で説明されているように、それぞれの親クラスから依存関係を受け取ることができます。

コンポーネントから依存関係を取得するには、@Inject アノテーションを使用してフィールド インジェクションを行います。

KotlinJava
@AndroidEntryPoint
class ExampleActivity : AppCompatActivity() {

  @Inject lateinit var analytics: AnalyticsAdapter
  ...
}
@AndroidEntryPoint
public class ExampleActivity extends AppCompatActivity {

  @Inject
  AnalyticsAdapter analytics;
  ...
}

Hilt が注入するクラスには、インジェクションを使用する他の基本クラスを含めることができます。抽象クラスの場合、@AndroidEntryPoint アノテーションは不要です。

Android クラスが注入されるライフサイクル コールバックの詳細については、コンポーネントのライフタイムをご覧ください。

Hilt バインディングを定義する

フィールド インジェクションを実行するには、対応するコンポーネントからの必要な依存関係のインスタンス提供方法を Hilt で把握している必要があります。バインディングには、型のインスタンスを依存関係として提供するために必要な情報が含まれています。

Hilt にバインディング情報を提供する方法の 1 つとして、コンストラクタ インジェクションがあります。クラスのコンストラクタで @Inject アノテーションを使用して、そのクラスのインスタンス提供方法を Hilt に知らせます。

KotlinJava
class AnalyticsAdapter @Inject constructor(
  private val service: AnalyticsService
) { ... }
public class AnalyticsAdapter {

  private final AnalyticsService service;

  @Inject
  AnalyticsAdapter(AnalyticsService service) {
    this.service = service;
  }
  ...
}

クラスのアノテーションが付けられたコンストラクタのパラメータは、そのクラスの依存関係です。この例では、AnalyticsAdapter に依存関係として AnalyticsService が指定されています。したがって、Hilt は AnalyticsService インスタンスの提供方法も把握している必要があります。

Hilt モジュール

場合によっては、型へのコンストラクタ インジェクションができないことがあります。これにはいくつかの理由が考えられます。たとえば、インターフェースの場合は、コンストラクタ インジェクションができません。また、外部ライブラリのクラスなど、自分が所有していない型の場合も、コンストラクタ インジェクションができません。このような場合は、Hilt モジュールを使用して、Hilt にバインディング情報を提供します。

Hilt モジュールは、@Module アノテーションが付けられたクラスです。Dagger モジュールと同じように、特定の型のインスタンスの提供方法を Hilt に知らせるものです。Dagger モジュールとは異なり、Hilt モジュールは @InstallIn アノテーションを付けて、各モジュールが使用またはインストールされる Android クラスを知らせる必要があります。

Hilt モジュールで提供する依存関係は、その Hilt モジュールをインストールした Android クラスに関連付けられている、すべての生成されたコンポーネントで使用できます。

@Binds を使用してインターフェース インスタンスを注入する

AnalyticsService の例を考えてみましょう。AnalyticsService がインターフェースの場合は、コンストラクタ インジェクションができません。代わりに、Hilt モジュール内に @Binds アノテーションを付けた抽象関数を作成して、バインディング情報を提供します。

@Binds アノテーションは、インターフェースのインスタンスを提供する必要がある場合に、どの実装を使用するかを知らせるものです。

アノテーションが付いた関数は、次の情報を Hilt に提供します。

  • 関数の戻り値の型で、その関数が提供するインターフェースのインスタンスを知らせます。
  • 関数のパラメータで、提供する実装を知らせます。
KotlinJava
interface AnalyticsService {
  fun analyticsMethods()
}

// Constructor-injected, because Hilt needs to know how to
// provide instances of AnalyticsServiceImpl, too.
class AnalyticsServiceImpl @Inject constructor(
  ...
) : AnalyticsService { ... }

@Module
@InstallIn(ActivityComponent::class)
abstract class AnalyticsModule {

  @Binds
  abstract fun bindAnalyticsService(
    analyticsServiceImpl: AnalyticsServiceImpl
  ): AnalyticsService
}
public interface AnalyticsService {
  void analyticsMethods();
}

// Constructor-injected, because Hilt needs to know how to
// provide instances of AnalyticsServiceImpl, too.
public class AnalyticsServiceImpl implements AnalyticsService {
  ...
  @Inject
  AnalyticsServiceImpl(...) {
    ...
  }
}

@Module
@InstallIn(ActivityComponent.class)
public abstract class AnalyticsModule {

  @Binds
  public abstract AnalyticsService bindAnalyticsService(
    AnalyticsServiceImpl analyticsServiceImpl
  );
}

Hilt モジュール AnalyticsModule@InstallIn(ActivityComponent.class) というアノテーションが付いています。これは、依存関係を ExampleActivity に注入させるためです。このアノテーションは、AnalyticsModule 内のすべての依存関係が、アプリのすべてのアクティビティ内で使用できることを意味します。

@Provides を使用してインスタンスを注入する

型のコンストラクタ インジェクションができないのはインターフェースだけではありません。自分で所有していないクラスも、外部ライブラリに含まれているため、コンストラクタ インジェクションができません(RetrofitOkHttpClientRoom データベースなど)。Builder パターンでインスタンスを作成する必要がある場合も同様です。

前の例で考えてみましょう。AnalyticsService クラスを直接所有しない場合は、Hilt モジュール内に関数を作成し、その関数に @Provides アノテーションを付けることにより、この型のインスタンスを提供できます。

アノテーション付きの関数は、次の情報を Hilt に提供します。

  • 関数の戻り値の型で、その関数が提供する型のインスタンスを知らせます。
  • 関数のパラメータは、対応する型の依存関係を知らせます。
  • 関数の本体は、対応する型のインスタンスの提供方法を知らせます。Hilt は、その型のインスタンスを提供する必要があるたびに関数の本体を実行します。
KotlinJava
@Module
@InstallIn(ActivityComponent::class)
object AnalyticsModule {

  @Provides
  fun provideAnalyticsService(
    // Potential dependencies of this type
  ): AnalyticsService {
      return Retrofit.Builder()
               .baseUrl("https://example.com")
               .build()
               .create(AnalyticsService::class.java)
  }
}
@Module
@InstallIn(ActivityComponent.class)
public class AnalyticsModule {

  @Provides
  public static AnalyticsService provideAnalyticsService(
    // Potential dependencies of this type
  ) {
      return new Retrofit.Builder()
               .baseUrl("https://example.com")
               .build()
               .create(AnalyticsService.class);
  }
}

同じ型に複数のバインディングを提供する

依存関係として複数の実装を同じ型で提供する必要がある場合は、Hilt で複数のバインディングを用意する必要があります。修飾子を使用することで、同じ型に対して複数のバインディングを定義できます。

修飾子は、1 つの型に複数のバインディングが定義されている場合に、そのタイプの特定のバインディングを識別するために使用するアノテーションです。

具体的な例で考えてみましょう。AnalyticsService の呼び出しをインターセプトする場合は、インターセプタが付いた OkHttpClient オブジェクトを使用できます。他のサービスには、別の方法で呼び出しをインターセプトする必要があります。この場合、OkHttpClient の異なる 2 つの実装の提供方法を Hilt に知らせる必要があります。

まず、@Binds メソッドまたは @Provides メソッドにアノテーションを付けるために使用する修飾子を定義します。

KotlinJava
@Qualifier
@Retention(AnnotationRetention.BINARY)
annotation class AuthInterceptorOkHttpClient

@Qualifier
@Retention(AnnotationRetention.BINARY)
annotation class OtherInterceptorOkHttpClient
@Qualifier
@Retention(RetentionPolicy.RUNTIME)
private @interface AuthInterceptorOkHttpClient {}

@Qualifier
@Retention(RetentionPolicy.RUNTIME)
private @interface OtherInterceptorOkHttpClient {}

次に、Hilt は各修飾子に対応する型のインスタンスの提供方法を把握する必要があります。この場合、@Provides が付いた Hilt モジュールを使用できます。2 つのメソッドでは戻り値の型が同じですが、修飾子によって異なるバインディングとしてラベル付けされます。

KotlinJava
@Module
@InstallIn(SingletonComponent::class)
object NetworkModule {

  @AuthInterceptorOkHttpClient
  @Provides
  fun provideAuthInterceptorOkHttpClient(
    authInterceptor: AuthInterceptor
  ): OkHttpClient {
      return OkHttpClient.Builder()
               .addInterceptor(authInterceptor)
               .build()
  }

  @OtherInterceptorOkHttpClient
  @Provides
  fun provideOtherInterceptorOkHttpClient(
    otherInterceptor: OtherInterceptor
  ): OkHttpClient {
      return OkHttpClient.Builder()
               .addInterceptor(otherInterceptor)
               .build()
  }
}
@Module
@InstallIn(ActivityComponent.class)
public class NetworkModule {

  @AuthInterceptorOkHttpClient
  @Provides
  public static OkHttpClient provideAuthInterceptorOkHttpClient(
    AuthInterceptor authInterceptor
  ) {
      return new OkHttpClient.Builder()
                   .addInterceptor(authInterceptor)
                   .build();
  }

  @OtherInterceptorOkHttpClient
  @Provides
  public static OkHttpClient provideOtherInterceptorOkHttpClient(
    OtherInterceptor otherInterceptor
  ) {
      return new OkHttpClient.Builder()
                   .addInterceptor(otherInterceptor)
                   .build();
  }
}

対応する修飾子でフィールドまたはパラメータをアノテーションすることにより、必要とする特定の型を注入できます。

KotlinJava
// As a dependency of another class.
@Module
@InstallIn(ActivityComponent::class)
object AnalyticsModule {

  @Provides
  fun provideAnalyticsService(
    @AuthInterceptorOkHttpClient okHttpClient: OkHttpClient
  ): AnalyticsService {
      return Retrofit.Builder()
               .baseUrl("https://example.com")
               .client(okHttpClient)
               .build()
               .create(AnalyticsService::class.java)
  }
}

// As a dependency of a constructor-injected class.
class ExampleServiceImpl @Inject constructor(
  @AuthInterceptorOkHttpClient private val okHttpClient: OkHttpClient
) : ...

// At field injection.
@AndroidEntryPoint
class ExampleActivity: AppCompatActivity() {

  @AuthInterceptorOkHttpClient
  @Inject lateinit var okHttpClient: OkHttpClient
}
// As a dependency of another class.
@Module
@InstallIn(ActivityComponent.class)
public class AnalyticsModule {

  @Provides
  public static AnalyticsService provideAnalyticsService(
    @AuthInterceptorOkHttpClient OkHttpClient okHttpClient
  ) {
      return new Retrofit.Builder()
                  .baseUrl("https://example.com")
                  .client(okHttpClient)
                  .build()
                  .create(AnalyticsService.class);
  }
}

// As a dependency of a constructor-injected class.
public class ExampleServiceImpl ... {

  private final OkHttpClient okHttpClient;

  @Inject
  ExampleServiceImpl(@AuthInterceptorOkHttpClient OkHttpClient okHttpClient) {
    this.okHttpClient = okHttpClient;
  }
}

// At field injection.
@AndroidEntryPoint
public class ExampleActivity extends AppCompatActivity {

  @AuthInterceptorOkHttpClient
  @Inject
  OkHttpClient okHttpClient;
  ...
}

ベスト プラクティスとして、型に修飾子を追加する場合は、その依存関係を提供するすべての方法に対して修飾子を追加することをおすすめします。基本実装または共通実装に修飾子を付けないと、エラーが発生しやすくなり、Hilt が誤った依存関係を注入するかもしれません。

Hilt で事前定義されている修飾子

Hilt では、いくつかの修飾子が事前定義されています。たとえば、アプリケーションまたはアクティビティから Context クラスが必要な場合のために、@ApplicationContext 修飾子と @ActivityContext 修飾子が用意されています。

例にある AnalyticsAdapter クラスでアクティビティのコンテキストが必要であるとします。次のコードは、AnalyticsAdapter にアクティビティのコンテキストの提供方法を示しています。

KotlinJava
class AnalyticsAdapter @Inject constructor(
    @ActivityContext private val context: Context,
    private val service: AnalyticsService
) { ... }
public class AnalyticsAdapter {

  private final Context context;
  private final AnalyticsService service;

  @Inject
  AnalyticsAdapter(
    @ActivityContext Context context,
    AnalyticsService service
  ) {
    this.context = context;
    this.service = service;
  }
}

Hilt で利用できる他の事前定義済みバインディングについては、コンポーネントのデフォルトのバインディングをご覧ください。

Android クラスに対して生成されたコンポーネント

フィールド インジェクションができる Android クラスでは、それぞれのクラスごとに、@InstallIn アノテーションで参照できる関連付けされた Hilt コンポーネントがあります。各 Hilt コンポーネントでは、対応する Android クラスにバインディングを注入する必要があります。

これまでの例では、Hilt モジュールでの ActivityComponent の使用方法を示しました。

Hilt には次のコンポーネントが用意されています。

Hilt コンポーネント インジェクションの対象
SingletonComponent Application
ActivityRetainedComponent なし
ViewModelComponent ViewModel
ActivityComponent Activity
FragmentComponent Fragment
ViewComponent View
ViewWithFragmentComponent @WithFragmentBindings アノテーションが付いた View
ServiceComponent Service

コンポーネントのライフタイム

Hilt は、対応する Android クラスのライフサイクルに従って、生成されたコンポーネント クラスのインスタンスを自動的に作成、破棄します。

生成されたコンポーネント 作成のタイミング 破棄のタイミング
SingletonComponent Application#onCreate() Application を破棄しました
ActivityRetainedComponent Activity#onCreate() Activity#onDestroy()
ViewModelComponent ViewModel を作成しました ViewModel を破棄しました
ActivityComponent Activity#onCreate() Activity#onDestroy()
FragmentComponent Fragment#onAttach() Fragment#onDestroy()
ViewComponent View#super() View を破棄しました
ViewWithFragmentComponent View#super() View を破棄しました
ServiceComponent Service#onCreate() Service#onDestroy()

コンポーネントのスコープ

デフォルトでは、Hilt のすべてのバインディングはスコープ設定されていません。つまり、アプリがバインディングをリクエストするたびに、必要な型の新しいインスタンスが作成されます。

例では、Hilt が AnalyticsAdapter を別の型の依存関係として提供するたびに、またはフィールド インジェクションを通じて(ExampleActivity として)提供するたびに、AnalyticsAdapter の新しいインスタンスが提供されます。

ただし、Hilt では、バインディングを特定のコンポーネントにスコープ設定することもできます。Hilt は、バインディングの対象となるコンポーネントのインスタンスごとに 1 回だけスコープ設定されたバインディングを作成します。そして、そのバインディングに対するすべてのリクエストで同じインスタンスを共有します。

次の表に、生成された各コンポーネントに対するスコープ アノテーションを示します。

Android クラス 生成されたコンポーネント スコープ
Application SingletonComponent @Singleton
Activity ActivityRetainedComponent @ActivityRetainedScoped
ViewModel ViewModelComponent @ViewModelScoped
Activity ActivityComponent @ActivityScoped
Fragment FragmentComponent @FragmentScoped
View ViewComponent @ViewScoped
@WithFragmentBindings アノテーションが付いた View ViewWithFragmentComponent @ViewScoped
Service ServiceComponent @ServiceScoped

例では、@ActivityScoped を使用して AnalyticsAdapterActivityComponent にスコープ設定している場合、対応するアクティビティが存続する間、常に AnalyticsAdapter の同じインスタンスが提供されます。

KotlinJava
@ActivityScoped
class AnalyticsAdapter @Inject constructor(
  private val service: AnalyticsService
) { ... }
@ActivityScoped
public class AnalyticsAdapter {

  private final AnalyticsService service;

  @Inject
  AnalyticsAdapter(AnalyticsService service) {
    this.service = service;
  }
  ...
}

AnalyticsService の内部状態が、ExampleActivity の中だけでなくアプリ内のどの場所でも同じインスタンスを使用する必要があるとします。この場合、AnalyticsServiceSingletonComponent にスコープ設定するのが適切です。こうすると、コンポーネントは、AnalyticsService のインスタンスの提供が必要となるたびに、毎回同じインスタンスを提供するようになります。

次の例は、Hilt モジュール内のコンポーネントにバインディングをスコープ設定する方法を示しています。バインディングのスコープは、インストール先のコンポーネントのスコープと一致する必要があるため、この例では ActivityComponent ではなく SingletonComponentAnalyticsService をインストールする必要があります。

KotlinJava
// If AnalyticsService is an interface.
@Module
@InstallIn(SingletonComponent::class)
abstract class AnalyticsModule {

  @Singleton
  @Binds
  abstract fun bindAnalyticsService(
    analyticsServiceImpl: AnalyticsServiceImpl
  ): AnalyticsService
}

// If you don't own AnalyticsService.
@Module
@InstallIn(SingletonComponent::class)
object AnalyticsModule {

  @Singleton
  @Provides
  fun provideAnalyticsService(): AnalyticsService {
      return Retrofit.Builder()
               .baseUrl("https://example.com")
               .build()
               .create(AnalyticsService::class.java)
  }
}
// If AnalyticsService is an interface.
@Module
@InstallIn(SingletonComponent.class)
public abstract class AnalyticsModule {

  @Singleton
  @Binds
  public abstract AnalyticsService bindAnalyticsService(
    AnalyticsServiceImpl analyticsServiceImpl
  );
}

// If you don't own AnalyticsService.
@Module
@InstallIn(SingletonComponent.class)
public class AnalyticsModule {

  @Singleton
  @Provides
  public static AnalyticsService provideAnalyticsService() {
      return new Retrofit.Builder()
               .baseUrl("https://example.com")
               .build()
               .create(AnalyticsService.class);
  }
}

Hilt コンポーネントのスコープについて詳しくは、Android と Hilt でのスコープ設定をご覧ください。

コンポーネント階層

モジュールをコンポーネントにインストールすると、コンポーネント内の他のバインディングの依存関係として、そのバインディングにアクセスできます。また、コンポーネント階層内で下位にある子コンポーネント内の他のバインディングの依存関係としてもアクセスできます。

ViewWithFragmentComponent は FragmentComponent の下にある。FragmentComponent と ViewComponent は、ActivityComponent の下にある。ActivityComponent は ActivityRetainedComponent の下にある。ViewModelComponent は ActivityRetainedComponent の下にある。ActivityRetainedComponent と ServiceComponent は SingletonComponent の下にある。
図 1. Hilt が生成するコンポーネント階層

コンポーネントのデフォルト バインディング

各 Hilt コンポーネントには、Hilt が独自のカスタム バインディングに依存関係として注入できるデフォルト バインディングのセットが用意されています。このバインディングは、特定のサブクラスではなく、一般のアクティビティ型とフラグメント型に対応しています。これは、Hilt では 1 つのアクティビティ コンポーネント定義を使用して、すべてのアクティビティを注入するためです。アクティビティごとに、このコンポーネントの異なるインスタンスがあります。

Android コンポーネント デフォルト バインディング
SingletonComponent Application
ActivityRetainedComponent Application
ViewModelComponent SavedStateHandle
ActivityComponent ApplicationActivity
FragmentComponent ApplicationActivityFragment
ViewComponent ApplicationActivityView
ViewWithFragmentComponent ApplicationActivityFragmentView
ServiceComponent ApplicationService

アプリケーション コンテキスト バインディングは @ApplicationContext でも利用できます。次に例を示します。

KotlinJava
class AnalyticsServiceImpl @Inject constructor(
  @ApplicationContext context: Context
) : AnalyticsService { ... }

// The Application binding is available without qualifiers.
class AnalyticsServiceImpl @Inject constructor(
  application: Application
) : AnalyticsService { ... }
public class AnalyticsServiceImpl implements AnalyticsService {

  private final Context context;

  @Inject
  AnalyticsAdapter(@ApplicationContext Context context) {
    this.context = context;
  }
}

// The Application binding is available without qualifiers.
public class AnalyticsServiceImpl implements AnalyticsService {

  private final Application application;

  @Inject
  AnalyticsAdapter(Application application) {
    this.application = application;
  }
}

アクティビティ コンテキスト バインディングは @ActivityContext でも利用できます。次に例を示します。

KotlinJava
class AnalyticsAdapter @Inject constructor(
  @ActivityContext context: Context
) { ... }

// The Activity binding is available without qualifiers.
class AnalyticsAdapter @Inject constructor(
  activity: FragmentActivity
) { ... }
public class AnalyticsAdapter {

  private final Context context;

  @Inject
  AnalyticsAdapter(@ActivityContext Context context) {
    this.context = context;
  }
}

// The Activity binding is available without qualifiers.
public class AnalyticsAdapter {

  private final FragmentActivity activity;

  @Inject
  AnalyticsAdapter(FragmentActivity activity) {
    this.activity = activity;
  }
}

Hilt でサポートされていないクラスに依存関係を注入する

Hilt では一般的な Android クラスのほとんどがサポートされています。Hilt でサポートされていないクラスには、フィールド インジェクションを行う必要があります。

そのような場合、@EntryPoint アノテーションを使用してエントリ ポイントを作成します。エントリ ポイントは、Hilt が管理するコードとそうでないコードの境界であり、コード内で Hilt で管理するオブジェクトのグラフが開始される最初のポイントです。エントリ ポイントを使用すると、Hilt が管理しないコードを使用して、依存関係グラフ内の依存関係を提供することができます。

たとえば、コンテンツ プロバイダは Hilt で直接サポートされません。コンテンツ プロバイダが Hilt を使用して依存関係を取得するようにしたい場合は、必要なバインディング タイプごとに @EntryPoint アノテーションが付いたインターフェースを定義し、修飾子を含める必要があります。さらに、次のように @InstallIn を追加して、エントリ ポイントをインストールするコンポーネントを指定します。

KotlinJava
class ExampleContentProvider : ContentProvider() {

  @EntryPoint
  @InstallIn(SingletonComponent::class)
  interface ExampleContentProviderEntryPoint {
    fun analyticsService(): AnalyticsService
  }

  ...
}
public class ExampleContentProvider extends ContentProvider {

  @EntryPoint
  @InstallIn(SingletonComponent.class)
  interface ExampleContentProviderEntryPoint {
    public AnalyticsService analyticsService();
  }
  ...
}

エントリ ポイントにアクセスするには、EntryPointAccessors の適切な静的メソッドを使用します。パラメータは、コンポーネント インスタンスか、コンポーネント ホルダーとして機能する @AndroidEntryPoint オブジェクトのいずれかです。パラメータとして渡すコンポーネントと EntryPointAccessors 静的メソッドの両方を、@EntryPoint インターフェースの @InstallIn アノテーションで指定した Android クラスと一致させてください。

KotlinJava
class ExampleContentProvider: ContentProvider() {
    ...

  override fun query(...): Cursor {
    val appContext = context?.applicationContext ?: throw IllegalStateException()
    val hiltEntryPoint =
      EntryPointAccessors.fromApplication(appContext, ExampleContentProviderEntryPoint::class.java)

    val analyticsService = hiltEntryPoint.analyticsService()
    ...
  }
}
public class ExampleContentProvider extends ContentProvider {

  @Override
  public Cursor query(...) {
    Context appContext = getContext().getApplicationContext();
    ExampleContentProviderEntryPoint hiltEntryPoint =
      EntryPointAccessors.fromApplication(appContext, ExampleContentProviderEntryPoint.class);
    AnalyticsService analyticsService = hiltEntryPoint.analyticsService();
  }
}

この例では、エントリ ポイントが SingletonComponent にインストールされているため、ApplicationContext を使用してエントリ ポイントを取得する必要があります。取得するバインディングが ActivityComponent にある場合は、代わりに ActivityContext を使用します。

Hilt と Dagger

Hilt は Dagger 上に構築されています。 Dagger を組み込む標準的な方法を提供する依存関係インジェクション ライブラリ 組み込むことが重要です

Dagger に関連する Hilt の目標は次のとおりです。

  • Android アプリを対象とした Dagger 関連のインフラストラクチャをシンプルなものにする。
  • コンポーネントとスコープの標準セットを作成して、設定とアプリ間でのコード共有を容易し、リーダビリティを向上させる。
  • テスト、デバッグ、リリースなど、さまざまなビルドタイプに対して異なるバインディングをプロビジョニングする簡単な方法を提供する。

Android オペレーティング システムは独自のフレームワーク クラスを数多くインスタンス化するため、Android アプリで Dagger を使用するには大量のボイラープレートを記述する必要があります。Hilt を使用すると、Android アプリで Dagger を使用するときに生じるボイラープレート コードを減らすことができます。Hilt では、次のものが自動的に生成され、提供されます。

  • Dagger を使用して Android フレームワークのクラスを統合するためのコンポーネント(手動で作成する必要がなくなります)。
  • Hilt が自動的に生成するコンポーネントで使用するスコープ アノテーション
  • ApplicationActivity などの Android クラスを表す事前定義されたバインディング
  • @ApplicationContext@ActivityContext を表す事前定義された修飾子

Dagger コードと Hilt コードは、同じコードベース内で共存できます。ただし、ほとんどの場合、Android 上での Dagger の使用は、すべて Hilt で管理することをおすすめします。Dagger を使用するプロジェクトを Hilt に移行するには、移行ガイドDagger アプリを Hilt に移行する(Codelab)をご覧ください。

参考情報

Hilt の詳細については、次の参考リンクをご覧ください。

サンプル

These samples showcase different architectural approaches to developing Android apps. In its different branches you'll find the same app (a TODO app) implemented with small differences. In this branch you'll find: User Interface built with Jetpack

Jetchat is a sample chat app built with Jetpack Compose. To try out this sample app, use the latest stable version of Android Studio. You can clone this repository or import the project from Android Studio following the steps here. This sample

Learn how this app was designed and built in the design case study, architecture learning journey and modularization learning journey. This is the repository for the Now in Android app. It is a work in progress 🚧. Now in Android is a fully functional

Codelabs

ブログ