AndroidでHiltをつかう(その2)


Androidで主に使用される依存性注入は、コンストラクタインジェクションとフィールドインジェクション。

インジェクションの基本

  1. Applicationクラスに「@HiltAndroidApp」をつける(Hiltが使用可能になる)
  2. フィールドインジェクションされたいクラスに「@AndroidEntryPoint」をつける
  3. フィールドに「@Inject」をつける(フィールドインジェクション)。lateinit宣言も必要
  4. バインディングしたいクラスのコンストラクタに「@Inject」をつける(コンストラクタインジェクション)

(注意1)
エントリポイントとは、Hilt が管理するコードとそうでないコードの境界
(注意2)
@AndroidEntryPointを付ける場合は、それに依存するAndroidクラスにもアノテーションを付ける必要がある。たとえば、フラグメントにアノテーションを付ける場合は、そのフラグメントを使用するアクティビティにもアノテーションを付ける必要がある

HiltでサポートされているAndroidクラス

クラス名
Application @HiltAndroidApp
Activity
Fragment
View
Service
BroadcastReceiver

スコープ

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

バインディングしたいクラスの上にスコープアノテーションをつける。スコープとAndroidクラスのライフサイクルとの関係は以下。

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

参考URL
https://developer.android.com/training/dependency-injection/hilt-android#generated-components

Hiltモジュール

インターフェース、ソースコード外のクラスは、コンストラクタインジェクションができない。このような場合は、Hiltモジュールを使用してバインディングする

(注意)
スコープが異なるコンテナに設定されている場合、同じモジュールを使用することはできない。

自分で所有していないクラスの場合

  1. @Moduleをつける
  2. @InstallInで、バインディングを使用できるコンテナ(コンポーネント)を指示
  3. 自分で所有していないクラスを返却するメソッドに@Providesをつける
@InstallIn(SingletonComponent::class)
@Module
object MyModule {
    @Provides
    fun f(@ApplicationContext appContext: Context): Context {
        return appContext
    }
}

Providesする関数の命名に法則はない。
返却する型が合致してさえいればよい。
コンストラクタインジェクションなのでバッティングすることはない。
InstallInは、SingletonComponent他、ActivityComponentがある

interfaceを返すクラスの場合

  1. Hiltモジュールファイルを作る(モジュールファイルには@Bindsアノテーションと@Providesアノテーションは共存できない)
  2. インジェクションしたいinterfaceを返すクラスに@Injectをつける(constructor()は省略できなくなる)
  3. インジェクションされる側のフィールドに@Injectをつける
@InstallIn(ActivityComponent::class)
@Module
abstract class NavigationModule {
    @Binds
    abstract fun bindNavigator(impl: AppNavigatorImpl): AppNavigator
}

同じ型の異なる実装(複数のバインディング)を提供する方法

@Qualifierで識別させる

@Qualifier
annotation class InMemoryLogger

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

    @InMemoryLogger//独自アノテーション
    @ActivityScoped
    @Binds
    abstract fun bindInMemoryLogger(impl: LoggerInMemoryDataSource): LoggerDataSource
}

ContentProviderにインジェクションする場合

ContentProviderはHiltでサポートされないため、エントリーポイントを@AndroidEntryPointで指定できない。

  1. ContentProvider内にをバインディングしたいコンポーネントを返すインターフェースを作成する
  2. 上記インターフェイスに@EntryPointをつける
  3. エントリポイントにアクセスするには、EntryPointAccessorsを使う
class LogsContentProvider: ContentProvider() {
    @InstallIn(SingletonComponent::class)
    @EntryPoint
    interface LCPEntryPoint {
        fun logDao(): LogDao
    }
    ・・・
    private fun getLogDao(appContext: Context): LogDao {
        val hiltEntryPoint = EntryPointAccessors.fromApplication(
            appContext,
            LCPEntryPoint::class.java
        )
        return hiltEntryPoint.logDao()
    }