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
230
Mobileアプリのアーキテクチャ設計法
kkagurazaka
2
1.3k
今後のJetpackでAndroid開発はこう変わる!
kkagurazaka
16
5.9k
外部SDKのViewにマスク処理をする方法と罠
kkagurazaka
0
840
AWAのフルリニューアルを支えたアーキテクチャ
kkagurazaka
1
800
CQRS Architecture on Android
kkagurazaka
7
2.8k
suspending functionの裏側
kkagurazaka
3
420
coroutinesで非同期ページネーション
kkagurazaka
1
590
async/awaitで快適非同期ライフ
kkagurazaka
4
1.8k
Other Decks in Programming
See All in Programming
カラム追加で増えるActiveRecordのメモリサイズ イメージできますか?
asayamakk
4
1.6k
Hotwire or React? ~Reactの録画機能をHotwireに置き換えて得られた知見~ / hotwire_or_react
harunatsujita
9
4.1k
【Kaigi on Rails 2024】YOUTRUST スポンサーLT
krpk1900
1
250
プロジェクト新規参入者のリードタイム短縮の観点から見る、品質の高いコードとアーキテクチャを保つメリット
d_endo
1
1k
役立つログに取り組もう
irof
27
8.7k
色々なIaCツールを実際に触って比較してみる
iriikeita
0
270
とにかくAWS GameDay!AWSは世界の共通言語! / Anyway, AWS GameDay! AWS is the world's lingua franca!
seike460
PRO
1
560
リリース8年目のサービスの1800個のERBファイルをViewComponentに移行した方法とその結果
katty0324
5
3.6k
Synchronizationを支える技術
s_shimotori
1
150
cXML という電子商取引の トランザクションを支える プロトコルと向きあっている話
phigasui
3
2.3k
僕がつくった48個のWebサービス達
yusukebe
18
17k
EventSourcingの理想と現実
wenas
6
2.1k
Featured
See All Featured
GraphQLとの向き合い方2022年版
quramy
43
13k
Sharpening the Axe: The Primacy of Toolmaking
bcantrill
37
1.8k
Evolution of real-time – Irina Nazarova, EuRuKo, 2024
irinanazarova
4
290
Fontdeck: Realign not Redesign
paulrobertlloyd
81
5.2k
For a Future-Friendly Web
brad_frost
175
9.4k
Designing for Performance
lara
604
68k
The Power of CSS Pseudo Elements
geoffreycrofte
72
5.3k
How GitHub (no longer) Works
holman
311
140k
Happy Clients
brianwarren
97
6.7k
The Straight Up "How To Draw Better" Workshop
denniskardys
232
140k
Designing for humans not robots
tammielis
249
25k
10 Git Anti Patterns You Should be Aware of
lemiorhan
654
59k
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!