Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
原理から完全理解するDagger Hilt Migration
Search
Keita Kagurazaka
October 20, 2021
Programming
1
1.9k
原理から完全理解するDagger Hilt Migration
DroidKaigi 2021の発表資料です
Keita Kagurazaka
October 20, 2021
Tweet
Share
More Decks by Keita Kagurazaka
See All by Keita Kagurazaka
三者三様 宣言的UI
kkagurazaka
0
440
SELECT FOR UPDATEの話
kkagurazaka
0
440
Mobileアプリのアーキテクチャ設計法
kkagurazaka
2
1.5k
今後のJetpackでAndroid開発はこう変わる!
kkagurazaka
16
6.3k
外部SDKのViewにマスク処理をする方法と罠
kkagurazaka
0
1k
AWAのフルリニューアルを支えたアーキテクチャ
kkagurazaka
1
940
CQRS Architecture on Android
kkagurazaka
7
3.1k
suspending functionの裏側
kkagurazaka
3
460
coroutinesで非同期ページネーション
kkagurazaka
1
680
Other Decks in Programming
See All in Programming
re:Invent 2025 トレンドからみる製品開発への AI Agent 活用
yoskoh
0
580
20251212 AI 時代的 Legacy Code 營救術 2025 WebConf
mouson
0
240
AI 駆動開発ライフサイクル(AI-DLC):ソフトウェアエンジニアリングの再構築 / AI-DLC Introduction
kanamasa
11
5k
LLM Çağında Backend Olmak: 10 Milyon Prompt'u Milisaniyede Sorgulamak
selcukusta
0
140
16年目のピクシブ百科事典を支える最新の技術基盤 / The Modern Tech Stack Powering Pixiv Encyclopedia in its 16th Year
ahuglajbclajep
2
450
TerraformとStrands AgentsでAmazon Bedrock AgentCoreのSSO認証付きエージェントを量産しよう!
neruneruo
4
2.3k
メルカリのリーダビリティチームが取り組む、AI時代のスケーラブルな品質文化
cloverrose
2
450
Graviton と Nitro と私
maroon1st
0
160
Kotlin Multiplatform Meetup - Compose Multiplatform 외부 의존성 아키텍처 설계부터 운영까지
wisemuji
0
160
DevFest Android in Korea 2025 - 개발자 커뮤니티를 통해 얻는 가치
wisemuji
0
180
脳の「省エネモード」をデバッグする ~System 1(直感)と System 2(論理)の切り替え~
panda728
PRO
0
130
SQL Server 2025 LT
odashinsuke
0
120
Featured
See All Featured
Fight the Zombie Pattern Library - RWD Summit 2016
marcelosomers
234
17k
Collaborative Software Design: How to facilitate domain modelling decisions
baasie
0
110
Cheating the UX When There Is Nothing More to Optimize - PixelPioneers
stephaniewalter
287
14k
How to Ace a Technical Interview
jacobian
281
24k
What Being in a Rock Band Can Teach Us About Real World SEO
427marketing
0
160
Crafting Experiences
bethany
0
26
Un-Boring Meetings
codingconduct
0
170
Stop Working from a Prison Cell
hatefulcrawdad
273
21k
Responsive Adventures: Dirty Tricks From The Dark Corners of Front-End
smashingmag
254
22k
Rails Girls Zürich Keynote
gr2m
95
14k
Leveraging LLMs for student feedback in introductory data science courses - posit::conf(2025)
minecr
0
100
Building Flexible Design Systems
yeseniaperezcruz
330
40k
Transcript
原理から完全理解する Dagger Hilt Migration Keita Kagurazaka
本発表について • 話すこと ◦ 素のDaggerとdagger.android、Hiltの違い ◦ Hiltへの移行方法 ◦ 移行する際によく当たる問題とその解決方法 •
話さないこと / 前提とする知識 ◦ DIとは何か、その必要性 ◦ Daggerの使い方、どんなクラスがあるかなど
Agenda Daggerの捉え方 dagger.androidとHiltの思想 Hiltに段階的に移行する FAQ 01 02 03 04
コンセプト • 「段階的」なHilt移行 ◦ 日々のプロジェクトと並行して進められるように • 原理から理解する ◦ 実際に移行する際に自ら壁を乗り越えられるように
Daggerの捉え方 dagger.androidとHiltの思想 Hiltに段階的に移行する FAQ Agenda 01 02 03 04
Dagger = Component Tree
Dagger = Component Tree
Componentの提供サービス 1. 依存性(インスタンス)の受け渡し 2. 依存性(インスタンス)の同一性保証
Componentの提供サービス 1. 依存性(インスタンス)の受け渡し 2. 依存性(インスタンス)の同一性保証 = 何回受け渡しても同じインスタンス
@Component(modules = [RepositoryModule::class]) interface ApplicationComponent { fun getUserRepository(): UserRepository fun
inject(app: App) } Componentによる依存性の受け渡し
@Component(modules = [RepositoryModule::class]) interface ApplicationComponent { fun getUserRepository(): UserRepository fun
inject(app: App) } Componentによる依存性の受け渡し
@Component(modules = [RepositoryModule::class]) interface ApplicationComponent { fun getUserRepository(): UserRepository fun
inject(app: App) } Componentによる依存性の受け渡し
@Singleton @Component(modules = [ApplicationModule::class]) interface ApplicationComponent { /* 省略 */
} @Module class ApplicationModule { @Singleton @Provides fun providesThirdPartyLibrary(): ThirdPartyLibrary = ThirdPartyLibrary.Builder.build() } Componentによる同一性保証
@Singleton @Component(modules = [ApplicationModule::class]) interface ApplicationComponent { /* 省略 */
} @Module class ApplicationModule { @Singleton @Provides fun providesThirdPartyLibrary(): ThirdPartyLibrary = ThirdPartyLibrary.Builder.build() } Componentによる同一性保証
@Singleton @Component(modules = [ApplicationModule::class]) interface ApplicationComponent { /* 省略 */
} @Module class ApplicationModule { @Singleton @Provides fun providesThirdPartyLibrary(): ThirdPartyLibrary = ThirdPartyLibrary.Builder.build() } Componentによる同一性保証
倉庫預かりオプション ThirdPartyLibrary ください ThirdParty Library 倉庫 ApplicationComponent 以前と同じ ブツです
@Singleton @Component(modules = [ApplicationModule::class]) interface ApplicationComponent { /* 省略 */
} @Module class ApplicationModule { @Singleton @Provides fun providesThirdPartyLibrary(): ThirdPartyLibrary = ThirdPartyLibrary.Builder.build() } Componentによる同一性保証
@Singleton @Component(modules = [ApplicationModule::class]) interface ApplicationComponent { /* 省略 */
} @Module class ApplicationModule { @Provides fun providesThirdPartyLibrary(): ThirdPartyLibrary = ThirdPartyLibrary.Builder.build() } Componentによる同一性保証 毎回作り直し!
Component = 工場 倉庫 受け渡し窓口
Dagger = Component Tree
インスタンスのライフタイム管理 アプリのプロセス終了まで 残ってるよ! ThirdParty Library ApplicationComponentが 消えるまで残ってるよ! 倉庫 ApplicationComponent
インスタンスのライフタイム管理 LoginActivityComponent ApplicationComponent アプリのプロセス終了まで 残ってるよ! LoginActivityが 消えるまで残ってるよ!
インスタンスのライフタイム管理 LoginActivityComponent ApplicationComponent 先輩のインスタンスも こちらで受け渡します!
インスタンスのライフタイム管理 LoginActivityComponent ApplicationComponent 苦しゅうない 先輩のインスタンスも こちらで受け渡します!
インスタンスのライフタイム管理 LoginActivityComponent ApplicationComponent Component Tree
@Scope @Retention(AnnotationRetention.RUNTIME) annotation class ActivityScope @ActivityScope @Subcomponent interface LoginActivityComponent {
fun inject(activity: LoginActivity) @Subcomponent.Factory interface Factory { fun create(): LoginActivityComponent } } SubcomponentによるTree定義
@Scope @Retention(AnnotationRetention.RUNTIME) annotation class ActivityScope @ActivityScope @Subcomponent interface LoginActivityComponent {
fun inject(activity: LoginActivity) @Subcomponent.Factory interface Factory { fun create(): LoginActivityComponent } } SubcomponentによるTree定義
@Module(subcomponents = [LoginActivityComponent::class]) interface ActivityModule @Singleton @Component(modules = [ ApplicationModule::class,
ActivityModule::class ]) interface ApplicationComponent SubcomponentによるTree定義
@Module(subcomponents = [LoginActivityComponent::class]) interface ActivityModule @Singleton @Component(modules = [ ApplicationModule::class,
ActivityModule::class ]) interface ApplicationComponent SubcomponentによるTree定義
@Module(subcomponents = [LoginActivityComponent::class]) interface ActivityModule @Singleton @Component(modules = [ ApplicationModule::class,
ActivityModule::class ]) interface ApplicationComponent SubcomponentによるTree定義
Component Tree ApplicationComponent ActivityModule LoginActivityComponent ApplicationModule
Component Tree ApplicationComponent ApplicationModule LoginActivityComponent
Component Tree ApplicationC ApplicationM LoginActivityC
Daggerの捉え方 dagger.androidとHiltの思想 Hiltに段階的に移行する FAQ Agenda 01 02 03 04
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問題
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問題
dagger.android
dagger.android 書くのが大変なら コード生成すればいいじゃない
@Module interface MainActivityModule { @ActivityScope @ContributesAndroidInjector fun contributesMainActivity(): MainActivity }
@Module( includes = [MainActivityModule::class], subcomponents = [LoginActivityComponent::class] ) interface ActivityModule dagger.androidの解法 - コード生成
@Module interface MainActivityModule { @ActivityScope @ContributesAndroidInjector fun contributesMainActivity(): MainActivity }
@Module( includes = [MainActivityModule::class], subcomponents = [LoginActivityComponent::class] ) interface ActivityModule dagger.androidの解法 - コード生成
@Module interface MainActivityModule { @ActivityScope @ContributesAndroidInjector fun contributesMainActivity(): MainActivity }
@Module( includes = [MainActivityModule::class], subcomponents = [LoginActivityComponent::class] ) interface ActivityModule dagger.androidの解法 - コード生成 MainActivitySubC
@Module interface MainActivityModule { @ActivityScope @ContributesAndroidInjector fun contributesMainActivity(): MainActivity }
@Module( includes = [MainActivityModule::class], subcomponents = [LoginActivityComponent::class] ) interface ActivityModule dagger.androidの解法 - コード生成
@Module interface MainActivityModule { @ActivityScope @ContributesAndroidInjector fun contributesMainActivity(): MainActivity }
@Module( includes = [MainActivityModule::class], subcomponents = [LoginActivityComponent::class] ) interface ActivityModule dagger.androidの解法 - コード生成 MainActivitySubC
@Module interface MainActivityModule { @ActivityScope @ContributesAndroidInjector fun contributesMainActivity(): MainActivity }
@Module( includes = [MainActivityModule::class], subcomponents = [LoginActivityComponent::class] ) interface ActivityModule dagger.androidの解法 - コード生成 ApplicationC MainActivitySubC
@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> {} } }
@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> {} } }
@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> {} } }
@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> {} } }
@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
@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
@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
dagger.androidのinjection
dagger.androidのinjection 1. AndroidInjection.inject
dagger.androidのinjection 1. AndroidInjection.inject DispatchingAndroidInjector
dagger.androidのinjection 1. AndroidInjection.inject DispatchingAndroidInjector ︙ MainActivitySubC MainActivity MainFragmentSubC MainFragment LoginActivitySubC
LoginActivity
dagger.androidのinjection 1. AndroidInjection.inject 2. 渡されたインスタンスのクラスを keyにSubCを見つける DispatchingAndroidInjector ︙ MainActivitySubC MainActivity
MainFragmentSubC MainFragment LoginActivitySubC LoginActivity
dagger.androidのinjection 1. AndroidInjection.inject 2. 渡されたインスタンスのクラスを keyにSubCを見つける 3. SubCをインスタンス化して injection DispatchingAndroidInjector
︙ MainActivitySubC MainActivity MainFragmentSubC MainFragment LoginActivitySubC LoginActivity
class App : Application(), HasAndroidInjector { @Inject lateinit var androidInjector:
DispatchingAndroidInjector<Any> override fun androidInjector(): AndroidInjector<Any> = androidInjector } dagger.androidのinjection
dagger.androidのComponent Tree ApplicationC MainActivitySubC MainFragmentSubC HogeFragmentSubC HugaActivitySubC HugaFragmentSubC PiyoFragmentSubC
Hilt
Hilt Componentは こっちで用意するから それ使ってね
https://dagger.dev/hilt/components より引用 Hiltの解法 - プリセットComponent
HiltのComponent Tree (simple ver.) SingletonC ActivityC FragmentC
@InstallIn(SingletonComponent::class) @Module class ApplicationModule { @Singleton @Provides fun providesThirdPartyLibrary(): ThirdPartyLibrary
= ThirdPartyLibrary.Builder.build() } プリセットComponentの使い方
@InstallIn(SingletonComponent::class) @Module class ApplicationModule { @Singleton @Provides fun providesThirdPartyLibrary(): ThirdPartyLibrary
= ThirdPartyLibrary.Builder.build() } プリセットComponentの使い方
@InstallIn(SingletonComponent::class) @Module class ApplicationModule @Singleton @Component(modules = [ApplicationModule::class]) interface ApplicationComponent
プリセットComponentの使い方
@Component(modules = [AllApplicationModule::class]) interface ApplicationComponent { fun inject(instance: NeedInjectionClass) fun
getUserRepository(): UserRepository } インスタンス受け渡し窓口閉鎖のご連絡
@InstallIn(SingletonComponent::class) @EntryPoint interface ApplicationEntryPoint { fun inject(instance: NeedInjectionClass) fun getUserRepository():
UserRepository } EntryPoint
@InstallIn(SingletonComponent::class) @EntryPoint interface ApplicationEntryPoint { fun inject(instance: NeedInjectionClass) fun getUserRepository():
UserRepository } public abstract static class SingletonC implements SingletonComponent, ApplicationEntryPoint { /* 省略 */ } EntryPoint = Componentの1窓口
interface RepositoryProvider { fun getUserRepository(): UserRepository } interface ApplicationInjector {
fun inject(instance: NeedInjectionClass) } @Singleton @Component(modules = [ApplicationModule::class]) interface ApplicationComponent : RepositoryProvider, ApplicationInjector EntryPoint ≈ Componentの分割定義
class MainActivity : AppCompatActivity() class MainFragment : Fragment() HiltのInjection
@AndroidEntryPoint class MainActivity : AppCompatActivity() @AndroidEntryPoint class MainFragment : Fragment()
HiltのInjection
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(); } }); } }
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(); } }); } }
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(); } }); } } ボイラープレート 全部ここでやります
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(); } }); } }
@AndroidEntryPoint class MainActivity : AppCompatActivity() Bytecode transformation in .kt
@AndroidEntryPoint class MainActivity : AppCompatActivity() Bytecode transformation in .kt
@AndroidEntryPoint class MainActivity : Hilt_MainActivity() Bytecode transformation in .dex
@AndroidEntryPoint class MainActivity : AppCompatActivity() @AndroidEntryPoint class MainFragment : Fragment()
HiltのInjection
Daggerの捉え方 dagger.androidとHiltの思想 Hiltに段階的に移行する FAQ Agenda 01 02 03 04
サンプルプロジェクト • https://github.com/k-kagurazaka/hilt-migration • main branch ◦ raw Dagger +
dagger.androidの構成 • hilt-migration branch ◦ Hilt化済み ◦ 各commitはbuild pass
サンプルプロジェクトのComponent Tree ApplicationC MainActivityC MainFragmentC LoginActivityC LoginFragmentC EmailSignupActivityC UserRegistrationFragmentC SnsSignupActivityC
UserRegistrationFragmentC
raw Dagger ApplicationC MainActivityC MainFragmentC LoginActivityC LoginFragmentC EmailSignupActivityC UserRegistrationFragmentC SnsSignupActivityC
UserRegistrationFragmentC
dagger.android ApplicationC MainActivityC MainFragmentC LoginActivityC LoginFragmentC EmailSignupActivityC UserRegistrationFragmentC SnsSignupActivityC UserRegistrationFragmentC
同一Fragment、別Component ApplicationC MainActivityC MainFragmentC LoginActivityC LoginFragmentC EmailSignupActivityC UserRegistrationFragmentC SnsSignupActivityC UserRegistrationFragmentC
移行スタート
移行ステップ 1. gradleの設定 2. Component Treeの接ぎ木 3. Scopeアノテーションの置き換え 4. MainActivityの移行
5. Loginの移行 6. MainFragmentの移行 7. Activityのお掃除 8. dagger.androidの除去 9. ApplicationComponentの削除
移行ステップ 1. gradleの設定 2. Component Treeの接ぎ木 3. Scopeアノテーションの置き換え 4. MainActivityの移行
5. Loginの移行 6. MainFragmentの移行 7. Activityのお掃除 8. dagger.androidの除去 9. ApplicationComponentの削除
SKIP
移行ステップ 1. gradleの設定 2. Component Treeの接ぎ木 3. Scopeアノテーションの置き換え 4. MainActivityの移行
5. Loginの移行 6. MainFragmentの移行 7. Activityのお掃除 8. dagger.androidの除去 9. ApplicationComponentの削除
Component Treeの接ぎ木 ApplicationC MainActivityC MainFragmentC LoginActivityC LoginFragmentC SingletonC ActivityC FragmentC
ApplicationM RepositoryM
Component Treeの接ぎ木 ApplicationC MainActivityC MainFragmentC LoginActivityC LoginFragmentC SingletonC ActivityC FragmentC
ApplicationM RepositoryM
@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の接ぎ木
@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の接ぎ木
@Singleton @Component(modules = [ AndroidInjectionModule::class, ApplicationModule::class, RepositoryModule::class, ActivityModule::class,]) interface ApplicationComponent
: AndroidInjector<App> { fun loginActivityComponent(): LoginActivityComponent.Factory } Component Treeの接ぎ木
@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の接ぎ木
@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の接ぎ木
SingletonCImpl Component Treeの接ぎ木 ApplicationC MainActivityC MainFragmentC LoginActivityC LoginFragmentC SingletonC ActivityC
FragmentC ApplicationM RepositoryM
SingletonCImpl Component Treeの接ぎ木 ApplicationC MainActivityC MainFragmentC LoginActivityC LoginFragmentC SingletonC ActivityC
FragmentC ApplicationM RepositoryM
@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の接ぎ木
@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の接ぎ木
SingletonCImpl Component Treeの接ぎ木 ApplicationC MainActivityC MainFragmentC LoginActivityC LoginFragmentC SingletonC ActivityC
FragmentC ApplicationM RepositoryM
class App : Application(), HasAndroidInjector { val component: ApplicationComponent =
DaggerApplicationComponent.factory().create(this) @Inject lateinit var androidInjector: DispatchingAndroidInjector<Any> override fun androidInjector(): AndroidInjector<Any> = androidInjector } Applicationの変更
class App : Application(), HasAndroidInjector { val component: ApplicationComponent =
DaggerApplicationComponent.factory().create(this) @Inject lateinit var androidInjector: DispatchingAndroidInjector<Any> override fun androidInjector(): AndroidInjector<Any> = androidInjector } Applicationの変更
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の変更
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の変更
@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の変更
@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の変更
@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の変更
SingletonCImpl 現時点のComponent Tree ApplicationC MainActivityC MainFragmentC LoginActivityC LoginFragmentC SingletonC ActivityC
FragmentC ApplicationM RepositoryM
現時点のComponent Tree ApplicationC MainActivityC MainFragmentC LoginActivityC LoginFragmentC SingletonC ActivityC FragmentC
ApplicationM RepositoryM
移行ステップ 1. gradleの設定 2. Component Treeの接ぎ木 3. Scopeアノテーションの置き換え 4. MainActivityの移行
5. Loginの移行 6. MainFragmentの移行 7. Activityのお掃除 8. dagger.androidの除去 9. ApplicationComponentの削除
@Module interface MainActivityModule { @ActivityScope @ContributesAndroidInjector(modules = [MainFragmentModule::class]) fun contributesMainActivity():
MainActivity } @Module interface MainFragmentModule { @FragmentScope @ContributesAndroidInjector fun contributesMainFragment(): MainFragment } Scopeアノテーションの置き換え
@Module interface MainActivityModule { @ActivityScope @ContributesAndroidInjector(modules = [MainFragmentModule::class]) fun contributesMainActivity():
MainActivity } @Module interface MainFragmentModule { @FragmentScope @ContributesAndroidInjector fun contributesMainFragment(): MainFragment } Scopeアノテーションの置き換え
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アノテーションの置き換え
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アノテーションの置き換え
移行ステップ 1. gradleの設定 2. Component Treeの接ぎ木 3. Scopeアノテーションの置き換え 4. MainActivityの移行
5. Loginの移行 6. MainFragmentの移行 7. Activityのお掃除 8. dagger.androidの除去 9. ApplicationComponentの削除
移行 = 担当Component変更 ApplicationC MainActivityC MainFragmentC LoginActivityC LoginFragmentC SingletonC ActivityC
FragmentC
移行 = 担当Component変更 ApplicationC MainActivityC MainFragmentC LoginActivityC LoginFragmentC SingletonC ActivityC
FragmentC MainActivity担当
移行 = 担当Component変更 ApplicationC MainActivityC MainFragmentC LoginActivityC LoginFragmentC SingletonC ActivityC
FragmentC MainActivity担当
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の移行
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の移行
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の移行
@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の移行
@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の移行
MainActivityのComponent削除 ApplicationC MainActivityC MainFragmentC LoginActivityC LoginFragmentC SingletonC ActivityC FragmentC
MainActivityのComponent削除 ApplicationC MainFragmentC LoginActivityC LoginFragmentC SingletonC ActivityC FragmentC
MainFragmentのComponent接ぎ木 ApplicationC MainFragmentC LoginActivityC LoginFragmentC SingletonC ActivityC FragmentC
MainFragmentのComponent接ぎ木 ApplicationC MainFragmentC LoginActivityC LoginFragmentC SingletonC ActivityC FragmentC
MainFragmentのComponent接ぎ木 ApplicationC MainFragmentC LoginActivityC LoginFragmentC SingletonC ActivityC FragmentC MainFragmentModule
@InstallIn(ActivityComponent::class) @Module( includes = [ MainFragmentModule::class, ] ) interface DaggerAndroidActivityModule
MainFragmentのComponent接ぎ木
移行ステップ 1. gradleの設定 2. Component Treeの接ぎ木 3. Scopeアノテーションの置き換え 4. MainActivityの移行
5. Loginの移行 6. MainFragmentの移行 7. Activityのお掃除 8. dagger.androidの除去 9. ApplicationComponentの削除
Loginの移行 ApplicationC MainFragmentC LoginActivityC LoginFragmentC SingletonC ActivityC FragmentC
Loginの移行 ApplicationC MainFragmentC LoginActivityC LoginFragmentC SingletonC ActivityC FragmentC
Loginの移行 ApplicationC MainFragmentC LoginActivityC LoginFragmentC SingletonC ActivityC FragmentC LoginActivity担当 LoginFragment担当
Loginの移行 ApplicationC MainFragmentC LoginActivityC LoginFragmentC SingletonC ActivityC FragmentC LoginActivity担当 LoginFragment担当
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の移行
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の移行
class LoginActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) } } LoginActivityの移行
@AndroidEntryPoint class LoginActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?)
{ super.onCreate(savedInstanceState) } } LoginActivityの移行
@AndroidEntryPoint class LoginActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?)
{ super.onCreate(savedInstanceState) /* 省略 */ } } LoginActivityの移行
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の移行
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の移行
class LoginFragment: Fragment() { override fun onAttach(context: Context) { super.onAttach(context)
} } LoginFragmentの移行
@AndroidEntryPoint class LoginFragment: Fragment() { override fun onAttach(context: Context) {
super.onAttach(context) } } LoginFragmentの移行
@AndroidEntryPoint class LoginFragment: Fragment() { override fun onAttach(context: Context) {
super.onAttach(context) /* 省略 */ } } LoginFragmentの移行
LoginのComponent削除 ApplicationC MainFragmentC LoginActivityC LoginFragmentC SingletonC ActivityC FragmentC
LoginのComponent削除 ApplicationC MainFragmentC SingletonC ActivityC FragmentC
LoginのComponent削除 ApplicationC MainFragmentC SingletonC ActivityC FragmentC
移行ステップ 1. gradleの設定 2. Component Treeの接ぎ木 3. Scopeアノテーションの置き換え 4. MainActivityの移行
5. Loginの移行 6. MainFragmentの移行 7. Activityのお掃除 8. dagger.androidの除去 9. ApplicationComponentの削除
MainFragmentの移行 ApplicationC MainFragmentC SingletonC ActivityC FragmentC MainFragment担当
MainFragmentの移行 ApplicationC MainFragmentC SingletonC ActivityC FragmentC MainFragment担当
class MainFragment : Fragment() { override fun onAttach(context: Context) {
AndroidSupportInjection.inject(this) super.onAttach(context) /* 省略 */ } } MainFragmentの移行
class MainFragment : Fragment() { override fun onAttach(context: Context) {
AndroidSupportInjection.inject(this) super.onAttach(context) /* 省略 */ } } MainFragmentの移行
class MainFragment : Fragment() { override fun onAttach(context: Context) {
super.onAttach(context) /* 省略 */ } } MainFragmentの移行
@AndroidEntryPoint class MainFragment : Fragment() { override fun onAttach(context: Context)
{ super.onAttach(context) /* 省略 */ } } MainFragmentの移行
@AndroidEntryPoint class MainFragment : Fragment() { override fun onAttach(context: Context)
{ super.onAttach(context) /* 省略 */ } } MainFragmentの移行
MainFragmentのComponent削除 ApplicationC MainFragmentC SingletonC ActivityC FragmentC
MainFragmentのComponent削除 ApplicationC SingletonC ActivityC FragmentC
移行ステップ 1. gradleの設定 2. Component Treeの接ぎ木 3. Scopeアノテーションの置き換え 4. MainActivityの移行
5. Loginの移行 6. MainFragmentの移行 7. Activityのお掃除 8. dagger.androidの除去 9. ApplicationComponentの削除
@AndroidEntryPoint class MainActivity : AppCompatActivity(), HasAndroidInjector { @Inject lateinit var
androidInjector: DispatchingAndroidInjector<Any> override fun androidInjector(): AndroidInjector<Any> = androidInjector /* 省略 */ } MainActivityからdagger.androidを除去
@AndroidEntryPoint class MainActivity : AppCompatActivity(), HasAndroidInjector { @Inject lateinit var
androidInjector: DispatchingAndroidInjector<Any> override fun androidInjector(): AndroidInjector<Any> = androidInjector /* 省略 */ } MainActivityからdagger.androidを除去
@AndroidEntryPoint class MainActivity : AppCompatActivity() { /* 省略 */ }
MainActivityからdagger.androidを除去
移行ステップ 1. gradleの設定 2. Component Treeの接ぎ木 3. Scopeアノテーションの置き換え 4. MainActivityの移行
5. Loginの移行 6. MainFragmentの移行 7. Activityのお掃除 8. dagger.androidの除去 9. ApplicationComponentの削除
Activity / Fragmentの移行が完了したら ApplicationC SingletonC ActivityC FragmentC
@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を除去
@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を除去
@HiltAndroidApp class App : Application() { val component: ApplicationComponent by
lazy { EntryPoints.get(this, ApplicationComponent::class.java) } } Appからdagger.androidを除去
@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を除去
@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を除去
@InstallIn(SingletonComponent::class) @EntryPoint interface ApplicationComponent @InstallIn(SingletonComponent::class) @Module( includes = [ ApplicationModule::class,
RepositoryModule::class, ] ) interface AggregatorModule Component Treeからdagger.androidを除去
移行ステップ 1. gradleの設定 2. Component Treeの接ぎ木 3. Scopeアノテーションの置き換え 4. MainActivityの移行
5. Loginの移行 6. MainFragmentの移行 7. Activityのお掃除 8. dagger.androidの除去 9. ApplicationComponentの削除
ApplicationComponent削除 ApplicationC SingletonC ActivityC FragmentC
ApplicationComponent削除 ApplicationC SingletonC ActivityC FragmentC
@HiltAndroidApp class App : Application() { val component: ApplicationComponent by
lazy { EntryPoints.get(this, ApplicationComponent::class.java) } } @InstallIn(SingletonComponent::class) @EntryPoint interface ApplicationComponent ApplicationComponentを削除
@HiltAndroidApp class App : Application() { val component: ApplicationComponent by
lazy { EntryPoints.get(this, ApplicationComponent::class.java) } } @InstallIn(SingletonComponent::class) @EntryPoint interface ApplicationComponent ApplicationComponentを削除
@HiltAndroidApp class App : Application() { /* 省略 */ }
ApplicationComponentを削除
Well done 👏
Daggerの捉え方 dagger.androidとHiltの思想 Hiltに段階的に移行する FAQ Agenda 01 02 03 04
Q. LoginActivityのような具象クラスを扱いたい @ActivityScope @Subcomponent interface LoginActivityComponent { @Subcomponent.Factory interface Factory
{ fun create(@BindsInstance activity: LoginActivity): LoginActivityComponent } }
Q. LoginActivityのような具象クラスを扱いたい A. castしましょう @InstallIn(ActivityComponent::class) @Module class LoginActivityModule { @Provides
fun providesLoginActivity(activity: Activity): LoginActivity? = activity as? LoginActivity? }
Q. LoginActivityのような具象クラスを扱いたい A. castしましょう @InstallIn(ActivityComponent::class) @Module class LoginFeatureModule { @Provides
fun providesLoginActivity(activity: Activity): LoginFeature? = (activity as? HasLoginFeature?)?.loginFeature }
Q. ActivityごとにModuleを切り替えたい @Module interface SignupActivityModule { @ActivityScope @ContributesAndroidInjector(modules = [EmailModule::class])
fun contributesEmailSignupActivity(): EmailSignupActivity @ActivityScope @ContributesAndroidInjector(modules = [SnsModule::class]) fun contributesSnsSignupActivity(): SnsSignupActivity }
Q. ActivityごとにModuleを切り替えたい @Module interface SignupActivityModule { @ActivityScope @ContributesAndroidInjector(modules = [EmailModule::class])
fun contributesEmailSignupActivity(): EmailSignupActivity @ActivityScope @ContributesAndroidInjector(modules = [SnsModule::class]) fun contributesSnsSignupActivity(): SnsSignupActivity }
Q. ActivityごとにModuleを切り替えたい @Module interface EmailModule { @Binds fun bindsFeature(feature: EmailSignupFeature):
SignupFeature } @Module interface SnsModule { @Binds fun bindsFeature(feature: SnsSignupFeature): SignupFeature }
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") } }
Q. FCMServiceにinjectするとUIテストが落ちるのですが @AndroidEntryPoint class MyFirebaseMessagingService : FirebaseMessagingService() { @Inject lateinit
var useCase: RegisterToken override fun onNewToken(token: String) { super.onNewToken(token) useCase.execute(token) } }
Q. FCMServiceにinjectするとUIテストが落ちるのですが C Tree @Before @After C Tree @Before @After
C Tree @Before @After
Q. FCMServiceにinjectするとUIテストが落ちるのですが ContentProvider 初期化 C Tree @Before @After C Tree
@Before @After C Tree @Before @After
Q. FCMServiceにinjectするとUIテストが落ちるのですが A. テストでuseCase実行を諦めましょう class MyFirebaseMessagingService : FirebaseMessagingService() { @EntryPoint
@InstallIn(SingletonComponent::class) interface LocalEntryPoint { fun useCase(): RegisterToken } }
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 } } }
Q. FCMServiceにinjectするとUIテストが落ちるのですが A. テストでuseCase実行を諦めましょう class MyFirebaseMessagingService : FirebaseMessagingService() { override
fun onNewToken(token: String) { super.onNewToken(token) useCase?.execute(token) } }
Q. 今度はApplicationでUIテストが落ちるのですが ContentProvider 初期化 C Tree @Before @After C Tree
@Before @After C Tree @Before @After
Q. 今度はApplicationでUIテストが落ちるのですが App#onCreate ContentProvider 初期化 C Tree @Before @After C
Tree @Before @After C Tree @Before @After
Q. 今度はApplicationでUIテストが落ちるのですが A. 初期化処理を遅延実行しましょう
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) } }
open class BaseApp : Application() { override fun onCreate() {
super.onCreate() initializeOnCreate() } protected open fun initializeOnCreate() { // ここに必要な初期化処理 } }
@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() } } }
@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() } } }
@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() } } }
@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() } } }
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() } } }
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() } } }
fun androidTestRule(testInstance: Any): TestRule = RuleChain.outerRule(HiltAndroidRule(testInstance)) .around(AppInitializeAndroidTestRule()) @HiltAndroidTest @RunWith(AndroidJUnit4::class) class
SomeUiTest { @get:Rule val rule = androidTestRule(this) }
Q. 〇〇を□□したい!
Q. 〇〇を□□したい! A. Component Treeを思い浮かべて、原理から考えよう
Thanks!