Android: Dagger HiltとDagger Androidの生成コードの違いについて

結論

(ドキュメントに書いてる通りです)

生成コードの違いについて

Dagger Hiltが爆誕したので、Dagger Androidと比較したときに、生成コードにはどのような違いがあるかを検証してみました。今回は、Activityのみに焦点を当てています。

Daggerのバージョンは2.28、Hiltのバージョンは2.28-alphaで検証しました。

Dagger Androidの生成コード

Dagger Androidでは、@ContributesAndroidInjectorを使うことで、ActivityのSubcomponentを自動生成してくれます。

例えば、MainActivityを指定すると、次のようなコードが生成されます。

@Module(subcomponents = MainActivityModule_ContributeMainActivity.MainActivitySubcomponent.class)
public abstract class MainActivityModule_ContributeMainActivity {
  private MainActivityModule_ContributeMainActivity() {}

  ...

  @Subcomponent
  public interface MainActivitySubcomponent extends AndroidInjector<MainActivity> {
    @Subcomponent.Factory
    interface Factory extends AndroidInjector.Factory<MainActivity> {}
  }
}

このSubcomponentは、MainActivity専用に作られていることが分かります。MainActivity専用に作っているので、このSubcomponent配下では、MainActivityを直接injectすることが可能です。

class MainCounter @Inject constructor(private val mainActivity: MainActivity) {...}

このMainCounterを他のActivityに対してinjectしようとすると、エラーが出ます。これが、専用のSubcomponentをActivity毎に独立で作るメリットです。

次に、Dagger Hiltを見てみます。

Dagger Hiltの生成コード

Dagger Hiltでは、@AndroidEntryPointを使うことで、ActivityのSubcomponentを自動生成してくれます。

例えば、MainActivityと、SubActivityを指定すると、次のようなコードが生成されます。

@Subcomponent(
    modules = {
        FragmentCBuilderModule.class,
        ViewCBuilderModule.class,
        DefaultViewModelFactories.ActivityModule.class,
        HiltWrapper_ActivityModule.class,
        MainActivityModule.class
    }
)
@ActivityScoped
public abstract static class ActivityC implements MainActivity_GeneratedInjector,
    SubActivity_GeneratedInjector,
    ActivityComponent,
    DefaultViewModelFactories.ActivityEntryPoint,
    FragmentComponentManager.FragmentComponentBuilderEntryPoint,
    ViewComponentManager.ViewComponentBuilderEntryPoint,
    GeneratedComponent {
  @Subcomponent.Builder
  abstract interface Builder extends ActivityComponentBuilder {
  }
}

Dagger Androidとの大きな差異は、MainActivityとSubActivityで共通のSubcomponentを持つことです。 これは、Dagger Hiltの設計によるもので、1つの大きなSubcomponentのほうが、何かと良いだろうという事でこのようになっています。

ただ、Dagger Androidと比較したときに、1つのSubcomponentになったことで、MainCounterのようなクラスを作りにくくなりました。 なぜなら、Subcomponentを共通化したことにより、MainActivityなどの専用のActivityを直接inject出来ないためです。

例えばDagger Hiltでは次のコードは違法です。

// 違法
class MainCounter @Inject constructor(private val mainActivity: MainActivity) {...}

ただ、抜け道もあり、型変換を行うことで回避出来ます。

@Module
@InstallIn(ActivityComponent::class)
object MainActivityModule {
  @Provides
  fun provideMainActivity(activity: Activity): MainActivity =
    activity as MainActivity
}

型変換は悪い匂いがするので、このようなコードは最小限に抑えるのが良さそうです。

まとめ

(ドキュメントに書いてる通りです)

Written by