Upgrade to Pro — share decks privately, control downloads, hide ads and more …

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

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

DroidKaigi 2021の発表資料です

Keita Kagurazaka

October 20, 2021
Tweet

More Decks by Keita Kagurazaka

Other Decks in Programming

Transcript

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

    話さないこと / 前提とする知識 ◦ DIとは何か、その必要性 ◦ Daggerの使い方、どんなクラスがあるかなど
  2. @Singleton @Component(modules = [ApplicationModule::class]) interface ApplicationComponent { /* 省略 */

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

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

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

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

    } @Module class ApplicationModule { @Provides fun providesThirdPartyLibrary(): ThirdPartyLibrary = ThirdPartyLibrary.Builder.build() } Componentによる同一性保証 毎回作り直し!
  7. @Scope @Retention(AnnotationRetention.RUNTIME) annotation class ActivityScope @ActivityScope @Subcomponent interface LoginActivityComponent {

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

    fun inject(activity: LoginActivity) @Subcomponent.Factory interface Factory { fun create(): LoginActivityComponent } } SubcomponentによるTree定義
  9. 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問題
  10. 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問題
  11. @Module interface MainActivityModule { @ActivityScope @ContributesAndroidInjector fun contributesMainActivity(): MainActivity }

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

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

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

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

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

    @Module( includes = [MainActivityModule::class], subcomponents = [LoginActivityComponent::class] ) interface ActivityModule dagger.androidの解法 - コード生成 ApplicationC MainActivitySubC
  17. @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> {} } }
  18. @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> {} } }
  19. @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> {} } }
  20. @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> {} } }
  21. @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
  22. @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
  23. @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
  24. class App : Application(), HasAndroidInjector { @Inject lateinit var androidInjector:

    DispatchingAndroidInjector<Any> override fun androidInjector(): AndroidInjector<Any> = androidInjector } dagger.androidのinjection
  25. @Component(modules = [AllApplicationModule::class]) interface ApplicationComponent { fun inject(instance: NeedInjectionClass) fun

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

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

    fun inject(instance: NeedInjectionClass) } @Singleton @Component(modules = [ApplicationModule::class]) interface ApplicationComponent : RepositoryProvider, ApplicationInjector EntryPoint ≈ Componentの分割定義
  28. 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(); } }); } }
  29. 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(); } }); } }
  30. 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(); } }); } } ボイラープレート 全部ここでやります
  31. 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(); } }); } }
  32. サンプルプロジェクト • https://github.com/k-kagurazaka/hilt-migration • main branch ◦ raw Dagger +

    dagger.androidの構成 • hilt-migration branch ◦ Hilt化済み ◦ 各commitはbuild pass
  33. 移行ステップ 1. gradleの設定 2. Component Treeの接ぎ木 3. Scopeアノテーションの置き換え 4. MainActivityの移行

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

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

    5. Loginの移行 6. MainFragmentの移行 7. Activityのお掃除 8. dagger.androidの除去 9. ApplicationComponentの削除
  36. @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の接ぎ木
  37. @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の接ぎ木
  38. @Singleton @Component(modules = [ AndroidInjectionModule::class, ApplicationModule::class, RepositoryModule::class, ActivityModule::class,]) interface ApplicationComponent

    : AndroidInjector<App> { fun loginActivityComponent(): LoginActivityComponent.Factory } Component Treeの接ぎ木
  39. @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の接ぎ木
  40. @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の接ぎ木
  41. @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の接ぎ木
  42. @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の接ぎ木
  43. class App : Application(), HasAndroidInjector { val component: ApplicationComponent =

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

    DaggerApplicationComponent.factory().create(this) @Inject lateinit var androidInjector: DispatchingAndroidInjector<Any> override fun androidInjector(): AndroidInjector<Any> = androidInjector } Applicationの変更
  45. 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の変更
  46. 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の変更
  47. @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の変更
  48. @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の変更
  49. @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の変更
  50. 移行ステップ 1. gradleの設定 2. Component Treeの接ぎ木 3. Scopeアノテーションの置き換え 4. MainActivityの移行

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

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

    MainActivity } @Module interface MainFragmentModule { @FragmentScope @ContributesAndroidInjector fun contributesMainFragment(): MainFragment } Scopeアノテーションの置き換え
  53. 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アノテーションの置き換え
  54. 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アノテーションの置き換え
  55. 移行ステップ 1. gradleの設定 2. Component Treeの接ぎ木 3. Scopeアノテーションの置き換え 4. MainActivityの移行

    5. Loginの移行 6. MainFragmentの移行 7. Activityのお掃除 8. dagger.androidの除去 9. ApplicationComponentの削除
  56. 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の移行
  57. 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の移行
  58. 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の移行
  59. @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の移行
  60. @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の移行
  61. 移行ステップ 1. gradleの設定 2. Component Treeの接ぎ木 3. Scopeアノテーションの置き換え 4. MainActivityの移行

    5. Loginの移行 6. MainFragmentの移行 7. Activityのお掃除 8. dagger.androidの除去 9. ApplicationComponentの削除
  62. 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の移行
  63. 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の移行
  64. class LoginActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) {

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

    { super.onCreate(savedInstanceState) /* 省略 */ } } LoginActivityの移行
  66. 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の移行
  67. 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の移行
  68. @AndroidEntryPoint class LoginFragment: Fragment() { override fun onAttach(context: Context) {

    super.onAttach(context) /* 省略 */ } } LoginFragmentの移行
  69. 移行ステップ 1. gradleの設定 2. Component Treeの接ぎ木 3. Scopeアノテーションの置き換え 4. MainActivityの移行

    5. Loginの移行 6. MainFragmentの移行 7. Activityのお掃除 8. dagger.androidの除去 9. ApplicationComponentの削除
  70. class MainFragment : Fragment() { override fun onAttach(context: Context) {

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

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

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

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

    { super.onAttach(context) /* 省略 */ } } MainFragmentの移行
  75. 移行ステップ 1. gradleの設定 2. Component Treeの接ぎ木 3. Scopeアノテーションの置き換え 4. MainActivityの移行

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

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

    androidInjector: DispatchingAndroidInjector<Any> override fun androidInjector(): AndroidInjector<Any> = androidInjector /* 省略 */ } MainActivityからdagger.androidを除去
  78. 移行ステップ 1. gradleの設定 2. Component Treeの接ぎ木 3. Scopeアノテーションの置き換え 4. MainActivityの移行

    5. Loginの移行 6. MainFragmentの移行 7. Activityのお掃除 8. dagger.androidの除去 9. ApplicationComponentの削除
  79. @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を除去
  80. @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を除去
  81. @HiltAndroidApp class App : Application() { val component: ApplicationComponent by

    lazy { EntryPoints.get(this, ApplicationComponent::class.java) } } Appからdagger.androidを除去
  82. @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を除去
  83. @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を除去
  84. 移行ステップ 1. gradleの設定 2. Component Treeの接ぎ木 3. Scopeアノテーションの置き換え 4. MainActivityの移行

    5. Loginの移行 6. MainFragmentの移行 7. Activityのお掃除 8. dagger.androidの除去 9. ApplicationComponentの削除
  85. @HiltAndroidApp class App : Application() { val component: ApplicationComponent by

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

    lazy { EntryPoints.get(this, ApplicationComponent::class.java) } } @InstallIn(SingletonComponent::class) @EntryPoint interface ApplicationComponent ApplicationComponentを削除
  87. Q. LoginActivityのような具象クラスを扱いたい A. castしましょう @InstallIn(ActivityComponent::class) @Module class LoginFeatureModule { @Provides

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

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

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

    SignupFeature } @Module interface SnsModule { @Binds fun bindsFeature(feature: SnsSignupFeature): SignupFeature }
  91. 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") } }
  92. Q. FCMServiceにinjectするとUIテストが落ちるのですが @AndroidEntryPoint class MyFirebaseMessagingService : FirebaseMessagingService() { @Inject lateinit

    var useCase: RegisterToken override fun onNewToken(token: String) { super.onNewToken(token) useCase.execute(token) } }
  93. 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 } } }
  94. 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) } }
  95. open class BaseApp : Application() { override fun onCreate() {

    super.onCreate() initializeOnCreate() } protected open fun initializeOnCreate() { // ここに必要な初期化処理 } }
  96. @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() } } }
  97. @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() } } }
  98. @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() } } }
  99. @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() } } }
  100. 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() } } }
  101. 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() } } }