Save 37% off PRO during our Black Friday Sale! »

原理から完全理解するDagger Hilt Migration

原理から完全理解するDagger Hilt Migration

DroidKaigi 2021の発表資料です

0f8842fabcd31a6c9007ddcd648247db?s=128

Keita Kagurazaka

October 20, 2021
Tweet

Transcript

  1. 原理から完全理解する Dagger Hilt Migration Keita Kagurazaka

  2. 本発表について • 話すこと ◦ 素のDaggerとdagger.android、Hiltの違い ◦ Hiltへの移行方法 ◦ 移行する際によく当たる問題とその解決方法 •

    話さないこと / 前提とする知識 ◦ DIとは何か、その必要性 ◦ Daggerの使い方、どんなクラスがあるかなど
  3. Agenda Daggerの捉え方 dagger.androidとHiltの思想 Hiltに段階的に移行する FAQ 01 02 03 04

  4. コンセプト • 「段階的」なHilt移行 ◦ 日々のプロジェクトと並行して進められるように • 原理から理解する ◦ 実際に移行する際に自ら壁を乗り越えられるように

  5. Daggerの捉え方 dagger.androidとHiltの思想 Hiltに段階的に移行する FAQ Agenda 01 02 03 04

  6. Dagger = Component Tree

  7. Dagger = Component Tree

  8. Componentの提供サービス 1. 依存性(インスタンス)の受け渡し 2. 依存性(インスタンス)の同一性保証

  9. Componentの提供サービス 1. 依存性(インスタンス)の受け渡し 2. 依存性(インスタンス)の同一性保証 = 何回受け渡しても同じインスタンス

  10. @Component(modules = [RepositoryModule::class]) interface ApplicationComponent { fun getUserRepository(): UserRepository fun

    inject(app: App) } Componentによる依存性の受け渡し
  11. @Component(modules = [RepositoryModule::class]) interface ApplicationComponent { fun getUserRepository(): UserRepository fun

    inject(app: App) } Componentによる依存性の受け渡し
  12. @Component(modules = [RepositoryModule::class]) interface ApplicationComponent { fun getUserRepository(): UserRepository fun

    inject(app: App) } Componentによる依存性の受け渡し
  13. @Singleton @Component(modules = [ApplicationModule::class]) interface ApplicationComponent { /* 省略 */

    } @Module class ApplicationModule { @Singleton @Provides fun providesThirdPartyLibrary(): ThirdPartyLibrary = ThirdPartyLibrary.Builder.build() } Componentによる同一性保証
  14. @Singleton @Component(modules = [ApplicationModule::class]) interface ApplicationComponent { /* 省略 */

    } @Module class ApplicationModule { @Singleton @Provides fun providesThirdPartyLibrary(): ThirdPartyLibrary = ThirdPartyLibrary.Builder.build() } Componentによる同一性保証
  15. @Singleton @Component(modules = [ApplicationModule::class]) interface ApplicationComponent { /* 省略 */

    } @Module class ApplicationModule { @Singleton @Provides fun providesThirdPartyLibrary(): ThirdPartyLibrary = ThirdPartyLibrary.Builder.build() } Componentによる同一性保証
  16. 倉庫預かりオプション ThirdPartyLibrary ください ThirdParty Library 倉庫 ApplicationComponent 以前と同じ ブツです

  17. @Singleton @Component(modules = [ApplicationModule::class]) interface ApplicationComponent { /* 省略 */

    } @Module class ApplicationModule { @Singleton @Provides fun providesThirdPartyLibrary(): ThirdPartyLibrary = ThirdPartyLibrary.Builder.build() } Componentによる同一性保証
  18. @Singleton @Component(modules = [ApplicationModule::class]) interface ApplicationComponent { /* 省略 */

    } @Module class ApplicationModule { @Provides fun providesThirdPartyLibrary(): ThirdPartyLibrary = ThirdPartyLibrary.Builder.build() } Componentによる同一性保証 毎回作り直し!
  19. Component = 工場 倉庫 受け渡し窓口

  20. Dagger = Component Tree

  21. インスタンスのライフタイム管理 アプリのプロセス終了まで 残ってるよ! ThirdParty Library ApplicationComponentが 消えるまで残ってるよ! 倉庫 ApplicationComponent

  22. インスタンスのライフタイム管理 LoginActivityComponent ApplicationComponent アプリのプロセス終了まで 残ってるよ! LoginActivityが 消えるまで残ってるよ!

  23. インスタンスのライフタイム管理 LoginActivityComponent ApplicationComponent 先輩のインスタンスも こちらで受け渡します!

  24. インスタンスのライフタイム管理 LoginActivityComponent ApplicationComponent 苦しゅうない 先輩のインスタンスも こちらで受け渡します!

  25. インスタンスのライフタイム管理 LoginActivityComponent ApplicationComponent Component Tree

  26. @Scope @Retention(AnnotationRetention.RUNTIME) annotation class ActivityScope @ActivityScope @Subcomponent interface LoginActivityComponent {

    fun inject(activity: LoginActivity) @Subcomponent.Factory interface Factory { fun create(): LoginActivityComponent } } SubcomponentによるTree定義
  27. @Scope @Retention(AnnotationRetention.RUNTIME) annotation class ActivityScope @ActivityScope @Subcomponent interface LoginActivityComponent {

    fun inject(activity: LoginActivity) @Subcomponent.Factory interface Factory { fun create(): LoginActivityComponent } } SubcomponentによるTree定義
  28. @Module(subcomponents = [LoginActivityComponent::class]) interface ActivityModule @Singleton @Component(modules = [ ApplicationModule::class,

    ActivityModule::class ]) interface ApplicationComponent SubcomponentによるTree定義
  29. @Module(subcomponents = [LoginActivityComponent::class]) interface ActivityModule @Singleton @Component(modules = [ ApplicationModule::class,

    ActivityModule::class ]) interface ApplicationComponent SubcomponentによるTree定義
  30. @Module(subcomponents = [LoginActivityComponent::class]) interface ActivityModule @Singleton @Component(modules = [ ApplicationModule::class,

    ActivityModule::class ]) interface ApplicationComponent SubcomponentによるTree定義
  31. Component Tree ApplicationComponent ActivityModule LoginActivityComponent ApplicationModule

  32. Component Tree ApplicationComponent ApplicationModule LoginActivityComponent

  33. Component Tree ApplicationC ApplicationM LoginActivityC

  34. Daggerの捉え方 dagger.androidとHiltの思想 Hiltに段階的に移行する FAQ Agenda 01 02 03 04

  35. interface ApplicationComponent { fun loginActivityComponent(): LoginActivityComponent.Factory } class LoginActivity :

    AppCompatActivity() { lateinit var component: LoginActivityComponent override fun onCreate(savedInstanceState: Bundle?) { component = (applicationContext as App).component .loginActivityComponent().create() component.inject(this) super.onCreate(savedInstanceState) } } Boilerplate問題
  36. interface ApplicationComponent { fun loginActivityComponent(): LoginActivityComponent.Factory } class LoginActivity :

    AppCompatActivity() { lateinit var component: LoginActivityComponent override fun onCreate(savedInstanceState: Bundle?) { component = (applicationContext as App).component .loginActivityComponent().create() component.inject(this) super.onCreate(savedInstanceState) } } Boilerplate問題
  37. dagger.android

  38. dagger.android 書くのが大変なら コード生成すればいいじゃない

  39. @Module interface MainActivityModule { @ActivityScope @ContributesAndroidInjector fun contributesMainActivity(): MainActivity }

    @Module( includes = [MainActivityModule::class], subcomponents = [LoginActivityComponent::class] ) interface ActivityModule dagger.androidの解法 - コード生成
  40. @Module interface MainActivityModule { @ActivityScope @ContributesAndroidInjector fun contributesMainActivity(): MainActivity }

    @Module( includes = [MainActivityModule::class], subcomponents = [LoginActivityComponent::class] ) interface ActivityModule dagger.androidの解法 - コード生成
  41. @Module interface MainActivityModule { @ActivityScope @ContributesAndroidInjector fun contributesMainActivity(): MainActivity }

    @Module( includes = [MainActivityModule::class], subcomponents = [LoginActivityComponent::class] ) interface ActivityModule dagger.androidの解法 - コード生成 MainActivitySubC
  42. @Module interface MainActivityModule { @ActivityScope @ContributesAndroidInjector fun contributesMainActivity(): MainActivity }

    @Module( includes = [MainActivityModule::class], subcomponents = [LoginActivityComponent::class] ) interface ActivityModule dagger.androidの解法 - コード生成
  43. @Module interface MainActivityModule { @ActivityScope @ContributesAndroidInjector fun contributesMainActivity(): MainActivity }

    @Module( includes = [MainActivityModule::class], subcomponents = [LoginActivityComponent::class] ) interface ActivityModule dagger.androidの解法 - コード生成 MainActivitySubC
  44. @Module interface MainActivityModule { @ActivityScope @ContributesAndroidInjector fun contributesMainActivity(): MainActivity }

    @Module( includes = [MainActivityModule::class], subcomponents = [LoginActivityComponent::class] ) interface ActivityModule dagger.androidの解法 - コード生成 ApplicationC MainActivitySubC
  45. @Module(subcomponents = MainActivityModule_ContributesMainActivity.MainActivitySubcomponent.class) public abstract class MainActivityModule_ContributesMainActivity { private MainActivityModule_ContributesMainActivity()

    {} @Binds @IntoMap @ClassKey(MainActivity.class) abstract AndroidInjector.Factory<?> bindAndroidInjectorFactory( MainActivitySubcomponent.Factory builder); @Subcomponent @ActivityScope public interface MainActivitySubcomponent extends AndroidInjector<MainActivity> { @Subcomponent.Factory interface Factory extends AndroidInjector.Factory<MainActivity> {} } }
  46. @Module(subcomponents = MainActivityModule_ContributesMainActivity.MainActivitySubcomponent.class) public abstract class MainActivityModule_ContributesMainActivity { private MainActivityModule_ContributesMainActivity()

    {} @Binds @IntoMap @ClassKey(MainActivity.class) abstract AndroidInjector.Factory<?> bindAndroidInjectorFactory( MainActivitySubcomponent.Factory builder); @Subcomponent @ActivityScope public interface MainActivitySubcomponent extends AndroidInjector<MainActivity> { @Subcomponent.Factory interface Factory extends AndroidInjector.Factory<MainActivity> {} } }
  47. @Module(subcomponents = MainActivityModule_ContributesMainActivity.MainActivitySubcomponent.class) public abstract class MainActivityModule_ContributesMainActivity { private MainActivityModule_ContributesMainActivity()

    {} @Binds @IntoMap @ClassKey(MainActivity.class) abstract AndroidInjector.Factory<?> bindAndroidInjectorFactory( MainActivitySubcomponent.Factory builder); @Subcomponent @ActivityScope public interface MainActivitySubcomponent extends AndroidInjector<MainActivity> { @Subcomponent.Factory interface Factory extends AndroidInjector.Factory<MainActivity> {} } }
  48. @Module(subcomponents = MainActivityModule_ContributesMainActivity.MainActivitySubcomponent.class) public abstract class MainActivityModule_ContributesMainActivity { private MainActivityModule_ContributesMainActivity()

    {} @Binds @IntoMap @ClassKey(MainActivity.class) abstract AndroidInjector.Factory<?> bindAndroidInjectorFactory( MainActivitySubcomponent.Factory builder); @Subcomponent @ActivityScope public interface MainActivitySubcomponent extends AndroidInjector<MainActivity> { @Subcomponent.Factory interface Factory extends AndroidInjector.Factory<MainActivity> {} } }
  49. @Module(subcomponents = MainActivityModule_ContributesMainActivity.MainActivitySubcomponent.class) public abstract class MainActivityModule_ContributesMainActivity { private MainActivityModule_ContributesMainActivity()

    {} @Binds @IntoMap @ClassKey(MainActivity.class) abstract AndroidInjector.Factory<?> bindAndroidInjectorFactory( MainActivitySubcomponent.Factory builder); @Subcomponent @ActivityScope public interface MainActivitySubcomponent extends AndroidInjector<MainActivity> { @Subcomponent.Factory interface Factory extends AndroidInjector.Factory<MainActivity> {} } } MainActivity
  50. @Module(subcomponents = MainActivityModule_ContributesMainActivity.MainActivitySubcomponent.class) public abstract class MainActivityModule_ContributesMainActivity { private MainActivityModule_ContributesMainActivity()

    {} @Binds @IntoMap @ClassKey(MainActivity.class) abstract AndroidInjector.Factory<?> bindAndroidInjectorFactory( MainActivitySubcomponent.Factory builder); @Subcomponent @ActivityScope public interface MainActivitySubcomponent extends AndroidInjector<MainActivity> { @Subcomponent.Factory interface Factory extends AndroidInjector.Factory<MainActivity> {} } } MainActivitySubC MainActivity
  51. @Module(subcomponents = MainActivityModule_ContributesMainActivity.MainActivitySubcomponent.class) public abstract class MainActivityModule_ContributesMainActivity { private MainActivityModule_ContributesMainActivity()

    {} @Binds @IntoMap @ClassKey(MainActivity.class) abstract AndroidInjector.Factory<?> bindAndroidInjectorFactory( MainActivitySubcomponent.Factory builder); @Subcomponent @ActivityScope public interface MainActivitySubcomponent extends AndroidInjector<MainActivity> { @Subcomponent.Factory interface Factory extends AndroidInjector.Factory<MainActivity> {} } } MainActivitySubC MainActivity
  52. dagger.androidのinjection

  53. dagger.androidのinjection 1. AndroidInjection.inject

  54. dagger.androidのinjection 1. AndroidInjection.inject DispatchingAndroidInjector

  55. dagger.androidのinjection 1. AndroidInjection.inject DispatchingAndroidInjector ︙ MainActivitySubC MainActivity MainFragmentSubC MainFragment LoginActivitySubC

    LoginActivity
  56. dagger.androidのinjection 1. AndroidInjection.inject 2. 渡されたインスタンスのクラスを keyにSubCを見つける DispatchingAndroidInjector ︙ MainActivitySubC MainActivity

    MainFragmentSubC MainFragment LoginActivitySubC LoginActivity
  57. dagger.androidのinjection 1. AndroidInjection.inject 2. 渡されたインスタンスのクラスを keyにSubCを見つける 3. SubCをインスタンス化して injection DispatchingAndroidInjector

    ︙ MainActivitySubC MainActivity MainFragmentSubC MainFragment LoginActivitySubC LoginActivity
  58. class App : Application(), HasAndroidInjector { @Inject lateinit var androidInjector:

    DispatchingAndroidInjector<Any> override fun androidInjector(): AndroidInjector<Any> = androidInjector } dagger.androidのinjection
  59. dagger.androidのComponent Tree ApplicationC MainActivitySubC MainFragmentSubC HogeFragmentSubC HugaActivitySubC HugaFragmentSubC PiyoFragmentSubC

  60. Hilt

  61. Hilt Componentは こっちで用意するから それ使ってね

  62. https://dagger.dev/hilt/components より引用 Hiltの解法 - プリセットComponent

  63. HiltのComponent Tree (simple ver.) SingletonC ActivityC FragmentC

  64. @InstallIn(SingletonComponent::class) @Module class ApplicationModule { @Singleton @Provides fun providesThirdPartyLibrary(): ThirdPartyLibrary

    = ThirdPartyLibrary.Builder.build() } プリセットComponentの使い方
  65. @InstallIn(SingletonComponent::class) @Module class ApplicationModule { @Singleton @Provides fun providesThirdPartyLibrary(): ThirdPartyLibrary

    = ThirdPartyLibrary.Builder.build() } プリセットComponentの使い方
  66. @InstallIn(SingletonComponent::class) @Module class ApplicationModule @Singleton @Component(modules = [ApplicationModule::class]) interface ApplicationComponent

    プリセットComponentの使い方
  67. @Component(modules = [AllApplicationModule::class]) interface ApplicationComponent { fun inject(instance: NeedInjectionClass) fun

    getUserRepository(): UserRepository } インスタンス受け渡し窓口閉鎖のご連絡
  68. @InstallIn(SingletonComponent::class) @EntryPoint interface ApplicationEntryPoint { fun inject(instance: NeedInjectionClass) fun getUserRepository():

    UserRepository } EntryPoint
  69. @InstallIn(SingletonComponent::class) @EntryPoint interface ApplicationEntryPoint { fun inject(instance: NeedInjectionClass) fun getUserRepository():

    UserRepository } public abstract static class SingletonC implements SingletonComponent, ApplicationEntryPoint { /* 省略 */ } EntryPoint = Componentの1窓口
  70. interface RepositoryProvider { fun getUserRepository(): UserRepository } interface ApplicationInjector {

    fun inject(instance: NeedInjectionClass) } @Singleton @Component(modules = [ApplicationModule::class]) interface ApplicationComponent : RepositoryProvider, ApplicationInjector EntryPoint ≈ Componentの分割定義
  71. class MainActivity : AppCompatActivity() class MainFragment : Fragment() HiltのInjection

  72. @AndroidEntryPoint class MainActivity : AppCompatActivity() @AndroidEntryPoint class MainFragment : Fragment()

    HiltのInjection
  73. public abstract class Hilt_MainActivity extends AppCompatActivity implements GeneratedComponentManagerHolder { Hilt_MainActivity()

    { super(); _initHiltInternal(); } private void _initHiltInternal() { addOnContextAvailableListener(new OnContextAvailableListener() { @Override public void onContextAvailable(Context context) { inject(); } }); } }
  74. public abstract class Hilt_MainActivity extends AppCompatActivity implements GeneratedComponentManagerHolder { Hilt_MainActivity()

    { super(); _initHiltInternal(); } private void _initHiltInternal() { addOnContextAvailableListener(new OnContextAvailableListener() { @Override public void onContextAvailable(Context context) { inject(); } }); } }
  75. public abstract class Hilt_MainActivity extends AppCompatActivity implements GeneratedComponentManagerHolder { Hilt_MainActivity()

    { super(); _initHiltInternal(); } private void _initHiltInternal() { addOnContextAvailableListener(new OnContextAvailableListener() { @Override public void onContextAvailable(Context context) { inject(); } }); } } ボイラープレート 全部ここでやります
  76. public abstract class Hilt_MainActivity extends AppCompatActivity implements GeneratedComponentManagerHolder { Hilt_MainActivity()

    { super(); _initHiltInternal(); } private void _initHiltInternal() { addOnContextAvailableListener(new OnContextAvailableListener() { @Override public void onContextAvailable(Context context) { inject(); } }); } }
  77. @AndroidEntryPoint class MainActivity : AppCompatActivity() Bytecode transformation in .kt

  78. @AndroidEntryPoint class MainActivity : AppCompatActivity() Bytecode transformation in .kt

  79. @AndroidEntryPoint class MainActivity : Hilt_MainActivity() Bytecode transformation in .dex

  80. @AndroidEntryPoint class MainActivity : AppCompatActivity() @AndroidEntryPoint class MainFragment : Fragment()

    HiltのInjection
  81. Daggerの捉え方 dagger.androidとHiltの思想 Hiltに段階的に移行する FAQ Agenda 01 02 03 04

  82. サンプルプロジェクト • https://github.com/k-kagurazaka/hilt-migration • main branch ◦ raw Dagger +

    dagger.androidの構成 • hilt-migration branch ◦ Hilt化済み ◦ 各commitはbuild pass
  83. サンプルプロジェクトのComponent Tree ApplicationC MainActivityC MainFragmentC LoginActivityC LoginFragmentC EmailSignupActivityC UserRegistrationFragmentC SnsSignupActivityC

    UserRegistrationFragmentC
  84. raw Dagger ApplicationC MainActivityC MainFragmentC LoginActivityC LoginFragmentC EmailSignupActivityC UserRegistrationFragmentC SnsSignupActivityC

    UserRegistrationFragmentC
  85. dagger.android ApplicationC MainActivityC MainFragmentC LoginActivityC LoginFragmentC EmailSignupActivityC UserRegistrationFragmentC SnsSignupActivityC UserRegistrationFragmentC

  86. 同一Fragment、別Component ApplicationC MainActivityC MainFragmentC LoginActivityC LoginFragmentC EmailSignupActivityC UserRegistrationFragmentC SnsSignupActivityC UserRegistrationFragmentC

  87. 移行スタート

  88. 移行ステップ 1. gradleの設定 2. Component Treeの接ぎ木 3. Scopeアノテーションの置き換え 4. MainActivityの移行

    5. Loginの移行 6. MainFragmentの移行 7. Activityのお掃除 8. dagger.androidの除去 9. ApplicationComponentの削除
  89. 移行ステップ 1. gradleの設定 2. Component Treeの接ぎ木 3. Scopeアノテーションの置き換え 4. MainActivityの移行

    5. Loginの移行 6. MainFragmentの移行 7. Activityのお掃除 8. dagger.androidの除去 9. ApplicationComponentの削除
  90. SKIP

  91. 移行ステップ 1. gradleの設定 2. Component Treeの接ぎ木 3. Scopeアノテーションの置き換え 4. MainActivityの移行

    5. Loginの移行 6. MainFragmentの移行 7. Activityのお掃除 8. dagger.androidの除去 9. ApplicationComponentの削除
  92. Component Treeの接ぎ木 ApplicationC MainActivityC MainFragmentC LoginActivityC LoginFragmentC SingletonC ActivityC FragmentC

    ApplicationM RepositoryM
  93. Component Treeの接ぎ木 ApplicationC MainActivityC MainFragmentC LoginActivityC LoginFragmentC SingletonC ActivityC FragmentC

    ApplicationM RepositoryM
  94. @Singleton @Component(modules = [ AndroidInjectionModule::class, ApplicationModule::class, RepositoryModule::class, ActivityModule::class,]) interface ApplicationComponent

    : AndroidInjector<App> { fun loginActivityComponent(): LoginActivityComponent.Factory @Component.Factory interface Factory { fun create(@BindsInstance app: App): ApplicationComponent } } Component Treeの接ぎ木
  95. @Singleton @Component(modules = [ AndroidInjectionModule::class, ApplicationModule::class, RepositoryModule::class, ActivityModule::class,]) interface ApplicationComponent

    : AndroidInjector<App> { fun loginActivityComponent(): LoginActivityComponent.Factory @Component.Factory interface Factory { fun create(@BindsInstance app: App): ApplicationComponent } } Component Treeの接ぎ木
  96. @Singleton @Component(modules = [ AndroidInjectionModule::class, ApplicationModule::class, RepositoryModule::class, ActivityModule::class,]) interface ApplicationComponent

    : AndroidInjector<App> { fun loginActivityComponent(): LoginActivityComponent.Factory } Component Treeの接ぎ木
  97. @Singleton @Component(modules = [ AndroidInjectionModule::class, ApplicationModule::class, RepositoryModule::class, ActivityModule::class,]) @InstallIn(SingletonComponent::class) @EntryPoint

    interface ApplicationComponent : AndroidInjector<App> { fun loginActivityComponent(): LoginActivityComponent.Factory } Component Treeの接ぎ木
  98. @Singleton @Component(modules = [ AndroidInjectionModule::class, ApplicationModule::class, RepositoryModule::class, ActivityModule::class,]) @InstallIn(SingletonComponent::class) @EntryPoint

    interface ApplicationComponent : AndroidInjector<App> { fun loginActivityComponent(): LoginActivityComponent.Factory } Component Treeの接ぎ木
  99. SingletonCImpl Component Treeの接ぎ木 ApplicationC MainActivityC MainFragmentC LoginActivityC LoginFragmentC SingletonC ActivityC

    FragmentC ApplicationM RepositoryM
  100. SingletonCImpl Component Treeの接ぎ木 ApplicationC MainActivityC MainFragmentC LoginActivityC LoginFragmentC SingletonC ActivityC

    FragmentC ApplicationM RepositoryM
  101. @Singleton @Component(modules = [ AndroidInjectionModule::class, ApplicationModule::class, RepositoryModule::class, ActivityModule::class,]) @InstallIn(SingletonComponent::class) @EntryPoint

    interface ApplicationComponent : AndroidInjector<App> { fun loginActivityComponent(): LoginActivityComponent.Factory } Component Treeの接ぎ木
  102. @InstallIn(SingletonComponent::class) @Module(includes = [ AndroidInjectionModule::class, ApplicationModule::class, RepositoryModule::class, ActivityModule::class,]) interface AggregatorModule

    @InstallIn(SingletonComponent::class) @EntryPoint interface ApplicationComponent : AndroidInjector<App> { fun loginActivityComponent(): LoginActivityComponent.Factory } Component Treeの接ぎ木
  103. SingletonCImpl Component Treeの接ぎ木 ApplicationC MainActivityC MainFragmentC LoginActivityC LoginFragmentC SingletonC ActivityC

    FragmentC ApplicationM RepositoryM
  104. class App : Application(), HasAndroidInjector { val component: ApplicationComponent =

    DaggerApplicationComponent.factory().create(this) @Inject lateinit var androidInjector: DispatchingAndroidInjector<Any> override fun androidInjector(): AndroidInjector<Any> = androidInjector } Applicationの変更
  105. class App : Application(), HasAndroidInjector { val component: ApplicationComponent =

    DaggerApplicationComponent.factory().create(this) @Inject lateinit var androidInjector: DispatchingAndroidInjector<Any> override fun androidInjector(): AndroidInjector<Any> = androidInjector } Applicationの変更
  106. class App : Application(), HasAndroidInjector { val component: ApplicationComponent by

    lazy { EntryPoints.get(this, ApplicationComponent::class.java) } @Inject lateinit var androidInjector: DispatchingAndroidInjector<Any> override fun androidInjector(): AndroidInjector<Any> = androidInjector } Applicationの変更
  107. class App : Application(), HasAndroidInjector { val component: ApplicationComponent by

    lazy { EntryPoints.get(this, ApplicationComponent::class.java) } @Inject lateinit var androidInjector: DispatchingAndroidInjector<Any> override fun androidInjector(): AndroidInjector<Any> = androidInjector } Applicationの変更
  108. @HiltAndroidApp class App : Application(), HasAndroidInjector { val component: ApplicationComponent

    by lazy { EntryPoints.get(this, ApplicationComponent::class.java) } @Inject lateinit var androidInjector: DispatchingAndroidInjector<Any> override fun androidInjector(): AndroidInjector<Any> = androidInjector } Applicationの変更
  109. @HiltAndroidApp class App : Application(), HasAndroidInjector { val component: ApplicationComponent

    by lazy { EntryPoints.get(this, ApplicationComponent::class.java) } @Inject lateinit var androidInjector: DispatchingAndroidInjector<Any> override fun androidInjector(): AndroidInjector<Any> = androidInjector } Applicationの変更
  110. @HiltAndroidApp class App : Application(), HasAndroidInjector { val component: ApplicationComponent

    by lazy { EntryPoints.get(this, ApplicationComponent::class.java) } @Inject lateinit var androidInjector: DispatchingAndroidInjector<Any> override fun androidInjector(): AndroidInjector<Any> = androidInjector } Applicationの変更
  111. SingletonCImpl 現時点のComponent Tree ApplicationC MainActivityC MainFragmentC LoginActivityC LoginFragmentC SingletonC ActivityC

    FragmentC ApplicationM RepositoryM
  112. 現時点のComponent Tree ApplicationC MainActivityC MainFragmentC LoginActivityC LoginFragmentC SingletonC ActivityC FragmentC

    ApplicationM RepositoryM
  113. 移行ステップ 1. gradleの設定 2. Component Treeの接ぎ木 3. Scopeアノテーションの置き換え 4. MainActivityの移行

    5. Loginの移行 6. MainFragmentの移行 7. Activityのお掃除 8. dagger.androidの除去 9. ApplicationComponentの削除
  114. @Module interface MainActivityModule { @ActivityScope @ContributesAndroidInjector(modules = [MainFragmentModule::class]) fun contributesMainActivity():

    MainActivity } @Module interface MainFragmentModule { @FragmentScope @ContributesAndroidInjector fun contributesMainFragment(): MainFragment } Scopeアノテーションの置き換え
  115. @Module interface MainActivityModule { @ActivityScope @ContributesAndroidInjector(modules = [MainFragmentModule::class]) fun contributesMainActivity():

    MainActivity } @Module interface MainFragmentModule { @FragmentScope @ContributesAndroidInjector fun contributesMainFragment(): MainFragment } Scopeアノテーションの置き換え
  116. import dagger.hilt.android.scopes.ActivityScoped import dagger.hilt.android.scopes.FragmentScoped @Module interface MainActivityModule { @ActivityScoped @ContributesAndroidInjector(modules

    = [MainFragmentModule::class]) fun contributesMainActivity(): MainActivity } @Module interface MainFragmentModule { @FragmentScoped @ContributesAndroidInjector fun contributesMainFragment(): MainFragment } Scopeアノテーションの置き換え
  117. import dagger.hilt.android.scopes.ActivityScoped import dagger.hilt.android.scopes.FragmentScoped @Module interface MainActivityModule { @ActivityScoped @ContributesAndroidInjector(modules

    = [MainFragmentModule::class]) fun contributesMainActivity(): MainActivity } @Module interface MainFragmentModule { @FragmentScoped @ContributesAndroidInjector fun contributesMainFragment(): MainFragment } Scopeアノテーションの置き換え
  118. 移行ステップ 1. gradleの設定 2. Component Treeの接ぎ木 3. Scopeアノテーションの置き換え 4. MainActivityの移行

    5. Loginの移行 6. MainFragmentの移行 7. Activityのお掃除 8. dagger.androidの除去 9. ApplicationComponentの削除
  119. 移行 = 担当Component変更 ApplicationC MainActivityC MainFragmentC LoginActivityC LoginFragmentC SingletonC ActivityC

    FragmentC
  120. 移行 = 担当Component変更 ApplicationC MainActivityC MainFragmentC LoginActivityC LoginFragmentC SingletonC ActivityC

    FragmentC MainActivity担当
  121. 移行 = 担当Component変更 ApplicationC MainActivityC MainFragmentC LoginActivityC LoginFragmentC SingletonC ActivityC

    FragmentC MainActivity担当
  122. class MainActivity : AppCompatActivity(), HasAndroidInjector { @Inject lateinit var androidInjector:

    DispatchingAndroidInjector<Any> override fun androidInjector(): AndroidInjector<Any> = androidInjector override fun onCreate(savedInstanceState: Bundle?) { AndroidInjection.inject(this) super.onCreate(savedInstanceState) } } MainActivityの移行
  123. class MainActivity : AppCompatActivity(), HasAndroidInjector { @Inject lateinit var androidInjector:

    DispatchingAndroidInjector<Any> override fun androidInjector(): AndroidInjector<Any> = androidInjector override fun onCreate(savedInstanceState: Bundle?) { AndroidInjection.inject(this) super.onCreate(savedInstanceState) } } MainActivityの移行
  124. class MainActivity : AppCompatActivity(), HasAndroidInjector { @Inject lateinit var androidInjector:

    DispatchingAndroidInjector<Any> override fun androidInjector(): AndroidInjector<Any> = androidInjector override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) } } MainActivityの移行
  125. @AndroidEntryPoint class MainActivity : AppCompatActivity(), HasAndroidInjector { @Inject lateinit var

    androidInjector: DispatchingAndroidInjector<Any> override fun androidInjector(): AndroidInjector<Any> = androidInjector override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) } } MainActivityの移行
  126. @AndroidEntryPoint class MainActivity : AppCompatActivity(), HasAndroidInjector { @Inject lateinit var

    androidInjector: DispatchingAndroidInjector<Any> override fun androidInjector(): AndroidInjector<Any> = androidInjector override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) } } MainActivityの移行
  127. MainActivityのComponent削除 ApplicationC MainActivityC MainFragmentC LoginActivityC LoginFragmentC SingletonC ActivityC FragmentC

  128. MainActivityのComponent削除 ApplicationC MainFragmentC LoginActivityC LoginFragmentC SingletonC ActivityC FragmentC

  129. MainFragmentのComponent接ぎ木 ApplicationC MainFragmentC LoginActivityC LoginFragmentC SingletonC ActivityC FragmentC

  130. MainFragmentのComponent接ぎ木 ApplicationC MainFragmentC LoginActivityC LoginFragmentC SingletonC ActivityC FragmentC

  131. MainFragmentのComponent接ぎ木 ApplicationC MainFragmentC LoginActivityC LoginFragmentC SingletonC ActivityC FragmentC MainFragmentModule

  132. @InstallIn(ActivityComponent::class) @Module( includes = [ MainFragmentModule::class, ] ) interface DaggerAndroidActivityModule

    MainFragmentのComponent接ぎ木
  133. 移行ステップ 1. gradleの設定 2. Component Treeの接ぎ木 3. Scopeアノテーションの置き換え 4. MainActivityの移行

    5. Loginの移行 6. MainFragmentの移行 7. Activityのお掃除 8. dagger.androidの除去 9. ApplicationComponentの削除
  134. Loginの移行 ApplicationC MainFragmentC LoginActivityC LoginFragmentC SingletonC ActivityC FragmentC

  135. Loginの移行 ApplicationC MainFragmentC LoginActivityC LoginFragmentC SingletonC ActivityC FragmentC

  136. Loginの移行 ApplicationC MainFragmentC LoginActivityC LoginFragmentC SingletonC ActivityC FragmentC LoginActivity担当 LoginFragment担当

  137. Loginの移行 ApplicationC MainFragmentC LoginActivityC LoginFragmentC SingletonC ActivityC FragmentC LoginActivity担当 LoginFragment担当

  138. class LoginActivity : AppCompatActivity() { lateinit var component: LoginActivityComponent override

    fun onCreate(savedInstanceState: Bundle?) { component = (applicationContext as App).component .loginActivityComponent().create(this) component.inject(this) super.onCreate(savedInstanceState) } } LoginActivityの移行
  139. class LoginActivity : AppCompatActivity() { lateinit var component: LoginActivityComponent override

    fun onCreate(savedInstanceState: Bundle?) { component = (applicationContext as App).component .loginActivityComponent().create(this) component.inject(this) super.onCreate(savedInstanceState) } } LoginActivityの移行
  140. class LoginActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) {

    super.onCreate(savedInstanceState) } } LoginActivityの移行
  141. @AndroidEntryPoint class LoginActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?)

    { super.onCreate(savedInstanceState) } } LoginActivityの移行
  142. @AndroidEntryPoint class LoginActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?)

    { super.onCreate(savedInstanceState) /* 省略 */ } } LoginActivityの移行
  143. class LoginFragment: Fragment() { lateinit var component: LoginFragmentComponent override fun

    onAttach(context: Context) { super.onAttach(context) component = (requireActivity() as LoginActivity).component .loginFragmentComponent().create(this) component.inject(this) } } LoginFragmentの移行
  144. class LoginFragment: Fragment() { lateinit var component: LoginFragmentComponent override fun

    onAttach(context: Context) { super.onAttach(context) component = (requireActivity() as LoginActivity).component .loginFragmentComponent().create(this) component.inject(this) } } LoginFragmentの移行
  145. class LoginFragment: Fragment() { override fun onAttach(context: Context) { super.onAttach(context)

    } } LoginFragmentの移行
  146. @AndroidEntryPoint class LoginFragment: Fragment() { override fun onAttach(context: Context) {

    super.onAttach(context) } } LoginFragmentの移行
  147. @AndroidEntryPoint class LoginFragment: Fragment() { override fun onAttach(context: Context) {

    super.onAttach(context) /* 省略 */ } } LoginFragmentの移行
  148. LoginのComponent削除 ApplicationC MainFragmentC LoginActivityC LoginFragmentC SingletonC ActivityC FragmentC

  149. LoginのComponent削除 ApplicationC MainFragmentC SingletonC ActivityC FragmentC

  150. LoginのComponent削除 ApplicationC MainFragmentC SingletonC ActivityC FragmentC

  151. 移行ステップ 1. gradleの設定 2. Component Treeの接ぎ木 3. Scopeアノテーションの置き換え 4. MainActivityの移行

    5. Loginの移行 6. MainFragmentの移行 7. Activityのお掃除 8. dagger.androidの除去 9. ApplicationComponentの削除
  152. MainFragmentの移行 ApplicationC MainFragmentC SingletonC ActivityC FragmentC MainFragment担当

  153. MainFragmentの移行 ApplicationC MainFragmentC SingletonC ActivityC FragmentC MainFragment担当

  154. class MainFragment : Fragment() { override fun onAttach(context: Context) {

    AndroidSupportInjection.inject(this) super.onAttach(context) /* 省略 */ } } MainFragmentの移行
  155. class MainFragment : Fragment() { override fun onAttach(context: Context) {

    AndroidSupportInjection.inject(this) super.onAttach(context) /* 省略 */ } } MainFragmentの移行
  156. class MainFragment : Fragment() { override fun onAttach(context: Context) {

    super.onAttach(context) /* 省略 */ } } MainFragmentの移行
  157. @AndroidEntryPoint class MainFragment : Fragment() { override fun onAttach(context: Context)

    { super.onAttach(context) /* 省略 */ } } MainFragmentの移行
  158. @AndroidEntryPoint class MainFragment : Fragment() { override fun onAttach(context: Context)

    { super.onAttach(context) /* 省略 */ } } MainFragmentの移行
  159. MainFragmentのComponent削除 ApplicationC MainFragmentC SingletonC ActivityC FragmentC

  160. MainFragmentのComponent削除 ApplicationC SingletonC ActivityC FragmentC

  161. 移行ステップ 1. gradleの設定 2. Component Treeの接ぎ木 3. Scopeアノテーションの置き換え 4. MainActivityの移行

    5. Loginの移行 6. MainFragmentの移行 7. Activityのお掃除 8. dagger.androidの除去 9. ApplicationComponentの削除
  162. @AndroidEntryPoint class MainActivity : AppCompatActivity(), HasAndroidInjector { @Inject lateinit var

    androidInjector: DispatchingAndroidInjector<Any> override fun androidInjector(): AndroidInjector<Any> = androidInjector /* 省略 */ } MainActivityからdagger.androidを除去
  163. @AndroidEntryPoint class MainActivity : AppCompatActivity(), HasAndroidInjector { @Inject lateinit var

    androidInjector: DispatchingAndroidInjector<Any> override fun androidInjector(): AndroidInjector<Any> = androidInjector /* 省略 */ } MainActivityからdagger.androidを除去
  164. @AndroidEntryPoint class MainActivity : AppCompatActivity() { /* 省略 */ }

    MainActivityからdagger.androidを除去
  165. 移行ステップ 1. gradleの設定 2. Component Treeの接ぎ木 3. Scopeアノテーションの置き換え 4. MainActivityの移行

    5. Loginの移行 6. MainFragmentの移行 7. Activityのお掃除 8. dagger.androidの除去 9. ApplicationComponentの削除
  166. Activity / Fragmentの移行が完了したら ApplicationC SingletonC ActivityC FragmentC

  167. @HiltAndroidApp class App : Application(), HasAndroidInjector { @Inject lateinit var

    androidInjector: DispatchingAndroidInjector<Any> override fun androidInjector(): AndroidInjector<Any> = androidInjector val component: ApplicationComponent by lazy { EntryPoints.get(this, ApplicationComponent::class.java) } } Appからdagger.androidを除去
  168. @HiltAndroidApp class App : Application(), HasAndroidInjector { @Inject lateinit var

    androidInjector: DispatchingAndroidInjector<Any> override fun androidInjector(): AndroidInjector<Any> = androidInjector val component: ApplicationComponent by lazy { EntryPoints.get(this, ApplicationComponent::class.java) } } Appからdagger.androidを除去
  169. @HiltAndroidApp class App : Application() { val component: ApplicationComponent by

    lazy { EntryPoints.get(this, ApplicationComponent::class.java) } } Appからdagger.androidを除去
  170. @InstallIn(SingletonComponent::class) @EntryPoint interface ApplicationComponent : AndroidInjector<App> @InstallIn(SingletonComponent::class) @Module( includes =

    [ AndroidInjectionModule::class, ApplicationModule::class, RepositoryModule::class, ] ) interface AggregatorModule Component Treeからdagger.androidを除去
  171. @InstallIn(SingletonComponent::class) @EntryPoint interface ApplicationComponent : AndroidInjector<App> @InstallIn(SingletonComponent::class) @Module( includes =

    [ AndroidInjectionModule::class, ApplicationModule::class, RepositoryModule::class, ] ) interface AggregatorModule Component Treeからdagger.androidを除去
  172. @InstallIn(SingletonComponent::class) @EntryPoint interface ApplicationComponent @InstallIn(SingletonComponent::class) @Module( includes = [ ApplicationModule::class,

    RepositoryModule::class, ] ) interface AggregatorModule Component Treeからdagger.androidを除去
  173. 移行ステップ 1. gradleの設定 2. Component Treeの接ぎ木 3. Scopeアノテーションの置き換え 4. MainActivityの移行

    5. Loginの移行 6. MainFragmentの移行 7. Activityのお掃除 8. dagger.androidの除去 9. ApplicationComponentの削除
  174. ApplicationComponent削除 ApplicationC SingletonC ActivityC FragmentC

  175. ApplicationComponent削除 ApplicationC SingletonC ActivityC FragmentC

  176. @HiltAndroidApp class App : Application() { val component: ApplicationComponent by

    lazy { EntryPoints.get(this, ApplicationComponent::class.java) } } @InstallIn(SingletonComponent::class) @EntryPoint interface ApplicationComponent ApplicationComponentを削除
  177. @HiltAndroidApp class App : Application() { val component: ApplicationComponent by

    lazy { EntryPoints.get(this, ApplicationComponent::class.java) } } @InstallIn(SingletonComponent::class) @EntryPoint interface ApplicationComponent ApplicationComponentを削除
  178. @HiltAndroidApp class App : Application() { /* 省略 */ }

    ApplicationComponentを削除
  179. Well done 👏

  180. Daggerの捉え方 dagger.androidとHiltの思想 Hiltに段階的に移行する FAQ Agenda 01 02 03 04

  181. Q. LoginActivityのような具象クラスを扱いたい @ActivityScope @Subcomponent interface LoginActivityComponent { @Subcomponent.Factory interface Factory

    { fun create(@BindsInstance activity: LoginActivity): LoginActivityComponent } }
  182. Q. LoginActivityのような具象クラスを扱いたい A. castしましょう @InstallIn(ActivityComponent::class) @Module class LoginActivityModule { @Provides

    fun providesLoginActivity(activity: Activity): LoginActivity? = activity as? LoginActivity? }
  183. Q. LoginActivityのような具象クラスを扱いたい A. castしましょう @InstallIn(ActivityComponent::class) @Module class LoginFeatureModule { @Provides

    fun providesLoginActivity(activity: Activity): LoginFeature? = (activity as? HasLoginFeature?)?.loginFeature }
  184. Q. ActivityごとにModuleを切り替えたい @Module interface SignupActivityModule { @ActivityScope @ContributesAndroidInjector(modules = [EmailModule::class])

    fun contributesEmailSignupActivity(): EmailSignupActivity @ActivityScope @ContributesAndroidInjector(modules = [SnsModule::class]) fun contributesSnsSignupActivity(): SnsSignupActivity }
  185. Q. ActivityごとにModuleを切り替えたい @Module interface SignupActivityModule { @ActivityScope @ContributesAndroidInjector(modules = [EmailModule::class])

    fun contributesEmailSignupActivity(): EmailSignupActivity @ActivityScope @ContributesAndroidInjector(modules = [SnsModule::class]) fun contributesSnsSignupActivity(): SnsSignupActivity }
  186. Q. ActivityごとにModuleを切り替えたい @Module interface EmailModule { @Binds fun bindsFeature(feature: EmailSignupFeature):

    SignupFeature } @Module interface SnsModule { @Binds fun bindsFeature(feature: SnsSignupFeature): SignupFeature }
  187. Q. ActivityごとにModuleを切り替えたい A. 切り替えたいのはModuleではなくインスタンス @InstallIn(ActivityComponent::class) @Module class SignupModule { @Provides

    fun providesFeature(activity: Activity): SignupFeature = when (activity) { is EmailSignupActivity -> EmailSignupFeature() is SnsSignupActivity -> SnsSignupFeature() else -> error("Invalid Activity") } }
  188. Q. FCMServiceにinjectするとUIテストが落ちるのですが @AndroidEntryPoint class MyFirebaseMessagingService : FirebaseMessagingService() { @Inject lateinit

    var useCase: RegisterToken override fun onNewToken(token: String) { super.onNewToken(token) useCase.execute(token) } }
  189. Q. FCMServiceにinjectするとUIテストが落ちるのですが C Tree @Before @After C Tree @Before @After

    C Tree @Before @After
  190. Q. FCMServiceにinjectするとUIテストが落ちるのですが ContentProvider 初期化 C Tree @Before @After C Tree

    @Before @After C Tree @Before @After
  191. Q. FCMServiceにinjectするとUIテストが落ちるのですが A. テストでuseCase実行を諦めましょう class MyFirebaseMessagingService : FirebaseMessagingService() { @EntryPoint

    @InstallIn(SingletonComponent::class) interface LocalEntryPoint { fun useCase(): RegisterToken } }
  192. Q. FCMServiceにinjectするとUIテストが落ちるのですが A. テストでuseCase実行を諦めましょう class MyFirebaseMessagingService : FirebaseMessagingService() { private

    val useCase: RegisterToken? by lazy { try { EntryPointAccessors.fromApplication( applicationContext, LocalEntryPoint::class.java ).useCase() } catch (t: Throwable) { null } } }
  193. Q. FCMServiceにinjectするとUIテストが落ちるのですが A. テストでuseCase実行を諦めましょう class MyFirebaseMessagingService : FirebaseMessagingService() { override

    fun onNewToken(token: String) { super.onNewToken(token) useCase?.execute(token) } }
  194. Q. 今度はApplicationでUIテストが落ちるのですが ContentProvider 初期化 C Tree @Before @After C Tree

    @Before @After C Tree @Before @After
  195. Q. 今度はApplicationでUIテストが落ちるのですが App#onCreate ContentProvider 初期化 C Tree @Before @After C

    Tree @Before @After C Tree @Before @After
  196. Q. 今度はApplicationでUIテストが落ちるのですが A. 初期化処理を遅延実行しましょう

  197. open class BaseApp : Application() { @EntryPoint @InstallIn(SingletonComponent::class) interface AppEntryPoint

    { fun appDependencies(): AppDependencies } private val entryPoint: AppEntryPoint by lazy { EntryPointAccessors.fromApplication(this, AppEntryPoint::class.java) } }
  198. open class BaseApp : Application() { override fun onCreate() {

    super.onCreate() initializeOnCreate() } protected open fun initializeOnCreate() { // ここに必要な初期化処理 } }
  199. @CustomTestApplication(AndroidTestApp::class) interface HiltAndroidTestApp class AndroidTestApp : BaseApp() { private val

    isInitialized = AtomicBoolean(false) override fun initializeOnCreate() { // 初期化はここでは実施しない } fun initialize() { if (isInitialized.compareAndSet(false, true)) { super.initializeOnCreate() } } }
  200. @CustomTestApplication(AndroidTestApp::class) interface HiltAndroidTestApp class AndroidTestApp : BaseApp() { private val

    isInitialized = AtomicBoolean(false) override fun initializeOnCreate() { // 初期化はここでは実施しない } fun initialize() { if (isInitialized.compareAndSet(false, true)) { super.initializeOnCreate() } } }
  201. @CustomTestApplication(AndroidTestApp::class) interface HiltAndroidTestApp class AndroidTestApp : BaseApp() { private val

    isInitialized = AtomicBoolean(false) override fun initializeOnCreate() { // 初期化はここでは実施しない } fun initialize() { if (isInitialized.compareAndSet(false, true)) { super.initializeOnCreate() } } }
  202. @CustomTestApplication(AndroidTestApp::class) interface HiltAndroidTestApp class AndroidTestApp : BaseApp() { private val

    isInitialized = AtomicBoolean(false) override fun initializeOnCreate() { // 初期化はここでは実施しない } fun initialize() { if (isInitialized.compareAndSet(false, true)) { super.initializeOnCreate() } } }
  203. class AppInitializeAndroidTestRule : TestRule { override fun apply(base: Statement, description:

    Description): Statement = object : Statement() { override fun evaluate() { val app = ApplicationProvider.getApplicationContext<AndroidTestApp>() app.initialize() base.evaluate() } } }
  204. class AppInitializeAndroidTestRule : TestRule { override fun apply(base: Statement, description:

    Description): Statement = object : Statement() { override fun evaluate() { val app = ApplicationProvider.getApplicationContext<AndroidTestApp>() app.initialize() base.evaluate() } } }
  205. fun androidTestRule(testInstance: Any): TestRule = RuleChain.outerRule(HiltAndroidRule(testInstance)) .around(AppInitializeAndroidTestRule()) @HiltAndroidTest @RunWith(AndroidJUnit4::class) class

    SomeUiTest { @get:Rule val rule = androidTestRule(this) }
  206. Q. 〇〇を□□したい!

  207. Q. 〇〇を□□したい! A. Component Treeを思い浮かべて、原理から考えよう

  208. Thanks!