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 Hilt Migration
    Keita Kagurazaka

    View Slide

  2. 本発表について
    ● 話すこと
    ○ 素のDaggerとdagger.android、Hiltの違い
    ○ Hiltへの移行方法
    ○ 移行する際によく当たる問題とその解決方法
    ● 話さないこと / 前提とする知識
    ○ DIとは何か、その必要性
    ○ Daggerの使い方、どんなクラスがあるかなど

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  6. Dagger = Component Tree

    View Slide

  7. Dagger = Component Tree

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  16. 倉庫預かりオプション
    ThirdPartyLibrary
    ください
    ThirdParty
    Library
    倉庫
    ApplicationComponent
    以前と同じ
    ブツです

    View Slide

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

    View Slide

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

    View Slide

  19. Component =
    工場
    倉庫
    受け渡し窓口

    View Slide

  20. Dagger = Component Tree

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  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定義

    View Slide

  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定義

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  31. Component Tree
    ApplicationComponent
    ActivityModule
    LoginActivityComponent
    ApplicationModule

    View Slide

  32. Component Tree
    ApplicationComponent
    ApplicationModule
    LoginActivityComponent

    View Slide

  33. Component Tree
    ApplicationC
    ApplicationM
    LoginActivityC

    View Slide

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

    View Slide

  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問題

    View Slide

  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問題

    View Slide

  37. dagger.android

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  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 {
    @Subcomponent.Factory
    interface Factory extends AndroidInjector.Factory {}
    }
    }

    View Slide

  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 {
    @Subcomponent.Factory
    interface Factory extends AndroidInjector.Factory {}
    }
    }

    View Slide

  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 {
    @Subcomponent.Factory
    interface Factory extends AndroidInjector.Factory {}
    }
    }

    View Slide

  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 {
    @Subcomponent.Factory
    interface Factory extends AndroidInjector.Factory {}
    }
    }

    View Slide

  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 {
    @Subcomponent.Factory
    interface Factory extends AndroidInjector.Factory {}
    }
    }
    MainActivity

    View Slide

  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 {
    @Subcomponent.Factory
    interface Factory extends AndroidInjector.Factory {}
    }
    }
    MainActivitySubC
    MainActivity

    View Slide

  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 {
    @Subcomponent.Factory
    interface Factory extends AndroidInjector.Factory {}
    }
    }
    MainActivitySubC
    MainActivity

    View Slide

  52. dagger.androidのinjection

    View Slide

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

    View Slide

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

    View Slide

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

    MainActivitySubC
    MainActivity
    MainFragmentSubC
    MainFragment
    LoginActivitySubC
    LoginActivity

    View Slide

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

    MainActivitySubC
    MainActivity
    MainFragmentSubC
    MainFragment
    LoginActivitySubC
    LoginActivity

    View Slide

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

    MainActivitySubC
    MainActivity
    MainFragmentSubC
    MainFragment
    LoginActivitySubC
    LoginActivity

    View Slide

  58. class App : Application(), HasAndroidInjector {
    @Inject
    lateinit var androidInjector: DispatchingAndroidInjector
    override fun androidInjector(): AndroidInjector =
    androidInjector
    }
    dagger.androidのinjection

    View Slide

  59. dagger.androidのComponent Tree
    ApplicationC
    MainActivitySubC
    MainFragmentSubC HogeFragmentSubC
    HugaActivitySubC
    HugaFragmentSubC PiyoFragmentSubC

    View Slide

  60. Hilt

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  67. @Component(modules = [AllApplicationModule::class])
    interface ApplicationComponent {
    fun inject(instance: NeedInjectionClass)
    fun getUserRepository(): UserRepository
    }
    インスタンス受け渡し窓口閉鎖のご連絡

    View Slide

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

    View Slide

  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窓口

    View Slide

  70. interface RepositoryProvider {
    fun getUserRepository(): UserRepository
    }
    interface ApplicationInjector {
    fun inject(instance: NeedInjectionClass)
    }
    @Singleton
    @Component(modules = [ApplicationModule::class])
    interface ApplicationComponent : RepositoryProvider, ApplicationInjector
    EntryPoint ≈ Componentの分割定義

    View Slide

  71. class MainActivity : AppCompatActivity()
    class MainFragment : Fragment()
    HiltのInjection

    View Slide

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

    View Slide

  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();
    }
    });
    }
    }

    View Slide

  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();
    }
    });
    }
    }

    View Slide

  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();
    }
    });
    }
    }
    ボイラープレート
    全部ここでやります

    View Slide

  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();
    }
    });
    }
    }

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  82. サンプルプロジェクト
    ● https://github.com/k-kagurazaka/hilt-migration
    ● main branch
    ○ raw Dagger + dagger.androidの構成
    ● hilt-migration branch
    ○ Hilt化済み
    ○ 各commitはbuild pass

    View Slide

  83. サンプルプロジェクトのComponent Tree
    ApplicationC
    MainActivityC MainFragmentC
    LoginActivityC LoginFragmentC
    EmailSignupActivityC UserRegistrationFragmentC
    SnsSignupActivityC UserRegistrationFragmentC

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  87. 移行スタート

    View Slide

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

    View Slide

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

    View Slide

  90. SKIP

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  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アノテーションの置き換え

    View Slide

  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アノテーションの置き換え

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  132. @InstallIn(ActivityComponent::class)
    @Module(
    includes = [
    MainFragmentModule::class,
    ]
    )
    interface DaggerAndroidActivityModule
    MainFragmentのComponent接ぎ木

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  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の移行

    View Slide

  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の移行

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  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の移行

    View Slide

  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の移行

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  148. LoginのComponent削除
    ApplicationC
    MainFragmentC
    LoginActivityC LoginFragmentC
    SingletonC ActivityC FragmentC

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  159. MainFragmentのComponent削除
    ApplicationC
    MainFragmentC
    SingletonC ActivityC FragmentC

    View Slide

  160. MainFragmentのComponent削除
    ApplicationC
    SingletonC ActivityC FragmentC

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  164. @AndroidEntryPoint
    class MainActivity : AppCompatActivity() {
    /* 省略 */
    }
    MainActivityからdagger.androidを除去

    View Slide

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

    View Slide

  166. Activity / Fragmentの移行が完了したら
    ApplicationC
    SingletonC ActivityC FragmentC

    View Slide

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

    View Slide

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

    View Slide

  169. @HiltAndroidApp
    class App : Application() {
    val component: ApplicationComponent by lazy {
    EntryPoints.get(this, ApplicationComponent::class.java)
    }
    }
    Appからdagger.androidを除去

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  174. ApplicationComponent削除
    ApplicationC
    SingletonC ActivityC FragmentC

    View Slide

  175. ApplicationComponent削除
    ApplicationC
    SingletonC ActivityC FragmentC

    View Slide

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

    View Slide

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

    View Slide

  178. @HiltAndroidApp
    class App : Application() {
    /* 省略 */
    }
    ApplicationComponentを削除

    View Slide

  179. Well done 👏

    View Slide

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

    View Slide

  181. Q. LoginActivityのような具象クラスを扱いたい
    @ActivityScope
    @Subcomponent
    interface LoginActivityComponent {
    @Subcomponent.Factory
    interface Factory {
    fun create(@BindsInstance activity: LoginActivity): LoginActivityComponent
    }
    }

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  186. Q. ActivityごとにModuleを切り替えたい
    @Module
    interface EmailModule {
    @Binds
    fun bindsFeature(feature: EmailSignupFeature): SignupFeature
    }
    @Module
    interface SnsModule {
    @Binds
    fun bindsFeature(feature: SnsSignupFeature): SignupFeature
    }

    View Slide

  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")
    }
    }

    View Slide

  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)
    }
    }

    View Slide

  189. Q. FCMServiceにinjectするとUIテストが落ちるのですが
    C Tree
    @Before @After
    C Tree
    @Before @After
    C Tree
    @Before @After

    View Slide

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

    View Slide

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

    View Slide

  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
    }
    }
    }

    View Slide

  193. Q. FCMServiceにinjectするとUIテストが落ちるのですが
    A. テストでuseCase実行を諦めましょう
    class MyFirebaseMessagingService : FirebaseMessagingService() {
    override fun onNewToken(token: String) {
    super.onNewToken(token)
    useCase?.execute(token)
    }
    }

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  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)
    }
    }

    View Slide

  198. open class BaseApp : Application() {
    override fun onCreate() {
    super.onCreate()
    initializeOnCreate()
    }
    protected open fun initializeOnCreate() {
    // ここに必要な初期化処理
    }
    }

    View Slide

  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()
    }
    }
    }

    View Slide

  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()
    }
    }
    }

    View Slide

  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()
    }
    }
    }

    View Slide

  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()
    }
    }
    }

    View Slide

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

    View Slide

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

    View Slide

  205. fun androidTestRule(testInstance: Any): TestRule =
    RuleChain.outerRule(HiltAndroidRule(testInstance))
    .around(AppInitializeAndroidTestRule())
    @HiltAndroidTest
    @RunWith(AndroidJUnit4::class)
    class SomeUiTest {
    @get:Rule
    val rule = androidTestRule(this)
    }

    View Slide

  206. Q. 〇〇を□□したい!

    View Slide

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

    View Slide

  208. Thanks!

    View Slide