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.7k
原理から完全理解するDagger Hilt Migration
DroidKaigi 2021の発表資料です
Keita Kagurazaka
October 20, 2021
Tweet
Share
More Decks by Keita Kagurazaka
See All by Keita Kagurazaka
SELECT FOR UPDATEの話
kkagurazaka
0
250
Mobileアプリのアーキテクチャ設計法
kkagurazaka
2
1.3k
今後のJetpackでAndroid開発はこう変わる!
kkagurazaka
16
5.9k
外部SDKのViewにマスク処理をする方法と罠
kkagurazaka
0
860
AWAのフルリニューアルを支えたアーキテクチャ
kkagurazaka
1
800
CQRS Architecture on Android
kkagurazaka
7
2.8k
suspending functionの裏側
kkagurazaka
3
420
coroutinesで非同期ページネーション
kkagurazaka
1
600
async/awaitで快適非同期ライフ
kkagurazaka
4
1.8k
Other Decks in Programming
See All in Programming
とにかくAWS GameDay!AWSは世界の共通言語! / Anyway, AWS GameDay! AWS is the world's lingua franca!
seike460
PRO
1
900
みんなでプロポーザルを書いてみた
yuriko1211
0
280
3 Effective Rules for Using Signals in Angular
manfredsteyer
PRO
0
100
距離関数を極める! / SESSIONS 2024
gam0022
0
290
Realtime API 入門
riofujimon
0
150
OSSで起業してもうすぐ10年 / Open Source Conference 2024 Shimane
furukawayasuto
0
110
Amazon Bedrock Agentsを用いてアプリ開発してみた!
har1101
0
340
Jakarta EE meets AI
ivargrimstad
0
620
EMになってからチームの成果を最大化するために取り組んだこと/ Maximize team performance as EM
nashiusagi
0
100
What’s New in Compose Multiplatform - A Live Tour (droidcon London 2024)
zsmb
1
480
Ethereum_.pdf
nekomatu
0
470
初めてDefinitelyTypedにPRを出した話
syumai
0
420
Featured
See All Featured
Practical Tips for Bootstrapping Information Extraction Pipelines
honnibal
PRO
10
720
Art, The Web, and Tiny UX
lynnandtonic
297
20k
Building an army of robots
kneath
302
43k
Making the Leap to Tech Lead
cromwellryan
133
8.9k
How To Stay Up To Date on Web Technology
chriscoyier
788
250k
Done Done
chrislema
181
16k
Music & Morning Musume
bryan
46
6.2k
Into the Great Unknown - MozCon
thekraken
32
1.5k
Building a Modern Day E-commerce SEO Strategy
aleyda
38
6.9k
How to train your dragon (web standard)
notwaldorf
88
5.7k
The Art of Programming - Codeland 2020
erikaheidi
52
13k
YesSQL, Process and Tooling at Scale
rocio
169
14k
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!