AndroidのDagger2で多階層のComponent構造を作った話


はじめに

事の起こりはFragmentでViewPagerを使っていた時。

上記のような、

  • 親Fragment(ViewPagerFragment)がViewPagerをレイアウトとして持ってて、
  • 親Fragmentが登録したい子Fragment(ChildFragment)のインスタンスを生成して登録する

ような構成は(多分)よくあると思う。

で、今回の要求は親Fragmentの生存期間と同等のインスタンスを子からも使いたい、というものだった。
具体的には以下の要素を満たすようにDIマップを作りたかった

  • 親Fragmentが生成されたときにインスタンスも生成される
  • 親Fragmentが破棄されたときにインスタンスも破棄される
  • 子Fragmentが切り替わっても同一のインスタンスを見る
  • 子Fragmentのライフサイクルに等しいインスタンスもある(これは子Fragment毎に生成)

こんな構成にしたかった理由は下記のようにSharedPresenterをParentFragmentやChildFragmentから呼び出したい、というニーズがあったから。

実装

こんな感じに実装したらいい感じになった


@AppScope
@Component(modules = [
   BindingModule::class:) 
interface AppComponent {
/*省略*/
}

@Module
abstract class BindingModule {
  @ActivityScope
  @ContributesAndroidInjector(modules = [ParentModule::class, SharedModule::class, ChildBindingModule::class])
    abstract fun bindingViewPagerFragment(): ViewPagerFragment
}

@Module
class ParentModule {
  @ParentScope
  @Provides
  internal fun provideParentPresenter(..): ParentPresenter =
            ParentPresenterImpl(..)
}

@Module
class ParentModule {
  @ParentScope
  @Provides
  internal fun provideSharedPresenter(..): SharedPresenter =
            SharedPresenterImpl(..)
}

@Module
abstract class ChildBindingModule {
    @ChildScope
    @ContributesAndroidInjector(modules = [(Child1Module::class)])
    abstract fun bindingChild1Fragment(): Child1Fragment

    @ChildScope
    @ContributesAndroidInjector(modules = [(Child2Module::class)])
    abstract fun bindingChild2Fragment(): Child2Fragment

    /*以下省略*/
}

@Module
class Child1Module {
  @ChildScope
  @Provides
  internal fun provideChild1Presenter(..): Child1Presenter =
            Child1PresenterImpl(..)
}

実装ポイント

ミソは以下のChildBindingModule

@Module
abstract class BindingModule {
  @ActivityScope
  @ContributesAndroidInjector(modules = [ParentModule::class, SharedModule::class, ChildBindingModule::class])
    abstract fun bindingParentFragment(): ParentFragment
}

親のFragmentとライフサイクルを同じにしたいものは親のContributesAndroidInjectorに追加する(ParentModuleSharedModule)。子のライフサイクルに登録したいものは、ChildBindingModuleの中に登録する。そしてChildBindingModuleの中のスコープをChildScopeにすることで実現できる。

こうすると以下のようなライフサイクルになる。子のライフサイクルは親のライフサイクルの配下にいる感じになるので、子からSharedModuleの要素にアクセスする場合は同じインスタンスを見るようになる。そして親が破棄されると子も一緒に破棄されるようになる。