$30 off During Our Annual Pro Sale. View Details »
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
今日から始める依存性の注入 / First Time Dependency Injection
Search
Keisuke Kobayashi
February 08, 2019
Programming
26
7.6k
今日から始める依存性の注入 / First Time Dependency Injection
DroidKaigi 2019
Room1, 2019/02/08 14:50~15:20
Keisuke Kobayashi
February 08, 2019
Tweet
Share
More Decks by Keisuke Kobayashi
See All by Keisuke Kobayashi
プロダクト開発をAI 1stに変革する〜SaaS is dead時代で生き残るために〜 / AI 1st Product Development
kobakei
0
1.8k
iOSアプリの技術的負債をどう返済したか / How to repay the technical debt of iOS app
kobakei
2
1k
iOSアプリ内で不正なSSL証明書を検知する / SSL Pinning for iOS apps
kobakei
34
12k
Kyashアプリ開発の現場
kobakei
4
2.9k
Review of Google I/O 2017 & Prepare for Google I/O 2018
kobakei
0
330
APIクライアントをCodableで置き換えた話
kobakei
0
1.6k
開発者が知っておきたい通知の歴史
kobakei
9
7.7k
mockito-kotlin
kobakei
1
540
2017年に新規アプリを立ち上げた話
kobakei
2
1.1k
Other Decks in Programming
See All in Programming
ViewファーストなRailsアプリ開発のたのしさ
sugiwe
0
500
Tinkerbellから学ぶ、Podで DHCPをリッスンする手法
tomokon
0
140
Cell-Based Architecture
larchanjo
0
130
Socio-Technical Evolution: Growing an Architecture and Its Organization for Fast Flow
cer
PRO
0
370
バックエンドエンジニアによる Amebaブログ K8s 基盤への CronJobの導入・運用経験
sunabig
0
160
俺流レスポンシブコーディング 2025
tak_dcxi
14
9k
sbt 2
xuwei_k
0
300
Navigating Dependency Injection with Metro
l2hyunwoo
1
140
안드로이드 9년차 개발자, 프론트엔드 주니어로 커리어 리셋하기
maryang
1
120
ELYZA_Findy AI Engineering Summit登壇資料_AIコーディング時代に「ちゃんと」やること_toB LLMプロダクト開発舞台裏_20251216
elyza
2
340
The Past, Present, and Future of Enterprise Java
ivargrimstad
0
200
Context is King? 〜Verifiability時代とコンテキスト設計 / Beyond "Context is King"
rkaga
10
1.3k
Featured
See All Featured
SEOcharity - Dark patterns in SEO and UX: How to avoid them and build a more ethical web
sarafernandezseo
0
82
How to Talk to Developers About Accessibility
jct
1
81
Between Models and Reality
mayunak
0
140
BBQ
matthewcrist
89
9.9k
Chrome DevTools: State of the Union 2024 - Debugging React & Beyond
addyosmani
9
1k
Joys of Absence: A Defence of Solitary Play
codingconduct
1
250
実際に使うSQLの書き方 徹底解説 / pgcon21j-tutorial
soudai
PRO
196
70k
Leadership Guide Workshop - DevTernity 2021
reverentgeek
0
160
The MySQL Ecosystem @ GitHub 2015
samlambert
251
13k
Money Talks: Using Revenue to Get Sh*t Done
nikkihalliwell
0
120
The Success of Rails: Ensuring Growth for the Next 100 Years
eileencodes
47
7.9k
Exploring the Power of Turbo Streams & Action Cable | RailsConf2023
kevinliebholz
37
6.2k
Transcript
ࠓ͔Β࢝ΊΔґଘੑͷೖ First Time Dependency Injection Keisuke Kobayashi DroidKaigi 2019 /
Room1, 2019/02/08 14:50~15:20
ࣗݾհ • Keisuke Kobayashi • Twitter: @kobakei122 • GitHub: @kobakei
• Merpay, Inc. / Engineering Manager • Studyplus, Inc. / ٕज़ސʢ෭ۀʣ
ࠓͷςʔϚ • DIॳ৺ऀɺ·ͨʮงғؾͰDagger2Λ͍ͬͯΔਓʯʹɺDI ͱͲ͏͍͏ͷ͔ɺԿͷͨΊʹಋೖ͢Δͷ͔Λղઆ͢Δηο γϣϯͰ͢ • ݸผͷDIίϯςφͷৄ͍͍͠ํʹ࣌ؒͷ্ؔਂೖΓ͠· ͤΜ
ΞδΣϯμ • Dependency Injection(DI)ͱʁ • AndroidΞϓϦ։ൃΛྫʹհ • DIίϯςφͷجຊతͳ͍ํ • Dagger2
& Koin • DIಋೖޙͷςετͷॻ͖ํ
Dependency Injection (DI)ͱʁ • ίϯϙʔωϯτؒͷґଘؔΛιʔείʔυ͔Βഉআ͠ɺ֎෦ ͔ΒґଘίϯϙʔωϯτΛೖͤ͞ΔσβΠϯύλʔϯ • ґଘؔΛഉআ͢Δ͜ͱͰɺίϯϙʔωϯτ͕ؒૄ݁߹ʹͳΔ
DIͷϝϦοτ • ίϯϙʔωϯτ͕ؒૄ݁߹ʹͳΔ͜ͱͰɺҎԼͷϝϦοτ͕͋ Δ • ΞϓϦέʔγϣϯΛ֦ு͘͢͠ͳΔ • ςετ͕ॻ͖͘͢ͳΔ • ͜ΕޙͰৄ͘͠ղઆ
AndroidΞϓϦ։ൃͰ Α͋͘ΔέʔεΛߟ͑Δ
Α͋͘ΔMVPͷྫ HogeActivity HogePresenter UserRepository ApiClient UserDao
Α͋͘ΔMVPͷྫ HogeActivity HogePresenter UserRepository ApiClient UserDao
// DIΛಋೖ͍ͯ͠ͳ͍ίʔυ // ApiClientͱUserDaoʹґଘͨ͠ϦϙδτϦΫϥε class UserRepository { private val apiClient:
ApiClient = ApiClientImpl() private val userDao: UserDao = UserDaoImpl() fun find(id:Long): Single<User> = return if (userDao.cached) { userDao.find(id) } else { apiClient.getUser(id) } } // UserRepositoryΛ͏ଆ val repo = UserRepository() repo.find(123)...
// DIΛಋೖ͍ͯ͠ͳ͍ίʔυ // ApiClientͱUserDaoʹґଘͨ͠ϦϙδτϦΫϥε class UserRepository { private val apiClient:
ApiClient = ApiClientImpl() private val userDao: UserDao = UserDaoImpl() fun find(id:Long): Single<User> = return if (userDao.cached) { userDao.find(id) } else { apiClient.getUser(id) } } // UserRepositoryΛ͏ଆ val repo = UserRepository() repo.find(123)... ґଘΦϒδΣΫτͱ ີ݁߹͍ͯ͠Δ
// ApiClientͱUserDaoʹґଘͨ͠ϦϙδτϦΫϥε class UserRepository( private val apiClient: ApiClient, private val
userDao: UserDao ) { fun find(id:Long): Single<User> = return if (userDao.cached) { userDao.find(id) } else { apiClient.getUser(id) } } // UserRepositoryΛ͏ଆ val apiClient = ApiClientImpl() val userDao = UserDaoImpl() val repo = UserRepository(apiClient, userDao) repo.find(123)...
// ApiClientͱUserDaoʹґଘͨ͠ϦϙδτϦΫϥε class UserRepository( private val apiClient: ApiClient, private val
userDao: UserDao ) { fun find(id:Long): Single<User> = return if (userDao.cached) { userDao.find(id) } else { apiClient.getUser(id) } } // UserRepositoryΛ͏ଆ val apiClient = ApiClientImpl() val userDao = UserDaoImpl() val repo = UserRepository(apiClient, userDao) repo.find(123)... ίϯετϥΫλͰ ґଘΦϒδΣΫτΛ ड͚औΔ
// ApiClientͱUserDaoʹґଘͨ͠ϦϙδτϦΫϥε class UserRepository( private val apiClient: ApiClient, private val
userDao: UserDao ) { fun find(id:Long): Single<User> = return if (userDao.cached) { userDao.find(id) } else { apiClient.getUser(id) } } // UserRepositoryΛ͏ଆ val apiClient = ApiClientImpl() val userDao = UserDaoImpl() val repo = UserRepository(apiClient, userDao) repo.find(123)... ͏ଆ͕ґଘΦϒδΣΫτ͔Β Έཱ͍ͯͯΔ
// ґଘΦϒδΣΫτΛղܾ͢ΔͨΊͷίϯςφΦϒδΣΫτ object Container { val apiClient: ApiClient = ApiClientImpl()
val userDao: UserDao = UserDaoImpl() val userRepo: UserRepository = UserRepository(apiClient, userDao) }
// ApiClientͱUserDaoʹґଘͨ͠ϦϙδτϦΫϥε class UserRepository( private val apiClient: ApiClient, private val
userDao: UserDao ) { fun find(id: Long): Single<User> = return if (userDao.cached) { userDao.find(id) } else { apiClient.getUser(id) } } // UserRepositoryΛ͏ଆ val repo = Container.userRepo repo.find(123)... UserRepositoryͷґଘؔ ͕ഉআ͞Εͨ
Α͋͘ΔMVPͷྫ HogeActivity HogePresenter UserRepository ApiClient UserDao
Activityͷ߹ • ίϯετϥΫλͰͷೖ͕Ͱ͖ͳ͍ • Fragment, Service, BroadcastReceiverͳͲಉ͡
// DIΛಋೖ͍ͯ͠ͳ͍ίʔυ class HogeActivity : AppCompatActivity() { private val presenter
= HogePresenter() override fun onCreate(savedInstanceState: Bundle?) { // লུ } fun onButtonClick(view: View) { presenter.onButtonClick() } } Activity͕ HogePresenterʹ ີ݁߹͍ͯ͠Δ
// DIΛಋೖ͍ͯ͠ͳ͍ίʔυ class HogeActivity : AppCompatActivity() { private lateinit var
presenter: HogePresenter override fun onCreate(savedInstanceState: Bundle?) { // লུ presenter = HogePresenter(applicationContext) } fun onButtonClick(view: View) { presenter.onButtonClick() } } ͜Εີ݁߹͍ͯ͠Δ
// ґଘΦϒδΣΫτΛղܾ͢ΔͨΊͷίϯςφΦϒδΣΫτ object Container { val apiClient: ApiClient = ApiClientImpl()
val userDao: UserDao = UserDaoImpl() val userRepo: UserRepository = UserRepository(apiClient, userDao) private var hogePresenter: HogePresenter? = null fun resolveHogePresenter(context: Context): HogePresenter { if (hogePresenter == null) { hogePresenter = HogePresenter(context, userRepo) } return requireNotNull(hogePresenter) } } ContextΛҾʹऔΔϝιουΛՃ
// DIΛಋೖޙͷίʔυ class HogeActivity : AppCompatActivity() { private val presenter:
HogePresenter by lazy { Container.resolveHogePresenter(applicationContext) } override fun onCreate(savedInstanceState: Bundle?) { // লུ }ɹ fun onButtonClick(view: View) { presenter.onButtonClick() } } by lazyΛ͏͜ͱͰɺ Application ContextΛͤΔ
͜͜ͰؾʹͳΔ͜ͱ͕
PresenterͷϥΠϑαΠΫϧ͜ΕͰେৎʁ • PresenterActivityͱಉ͡ϥΠϑαΠΫϧʹ͍ͨ͠ • Activity͕ੜ͞ΕΔͱPresenterੜ͞ΕɺActivity͕ഁغ ͞ΕΔͱಉ࣌ʹഁغ͞ΕΔ͖ • ActivityͷΠϯελϯε͕2ͭ͋Δͱ͖ɺͦΕͧΕʹ Presenter͕͍ͯ΄͍͠
είʔϓ • ґଘΦϒδΣΫτͷੜଘظؒΛද͢ • SingletonɺActivityͱಉ͡ੜଘظؒɺͳͲ • είʔϓ͝ͱʹґଘΦϒδΣΫτ͕ੜ͞Εɺ͍ճ͞ΕΔΑ ͏ͳΈ͕ඞཁ
HogeActivity HogePresenter UserRepository ApiClient UserDao FugaActivity FugaPresenter
HogeActivity HogePresenter UserRepository ApiClient UserDao FugaActivity FugaPresenter ը໘ڞ௨Ͱ͍·Θ͍ͨ͠ ֤ը໘͝ͱʹੜ͍ͨ͠
HogeActivity HogePresenter UserRepository ApiClient UserDao FugaActivity FugaPresenter Singleton Singleton Singleton
ActivityScope ActivityScope
object Container { // লུ val userRepo = UserRepository(apiClient, logger)
} class HogeActivityScopeContainer { fun resolveHogePresenter(ctx: Context): HogePresenter {…} } class FugaActivityScopeContainer { fun resolveFugaPresenter(ctx: Context): FugaPresenter {…} }
object Container { // লུ val userRepo = UserRepository(apiClient, logger)
} class HogeActivityScopeContainer { fun resolveHogePresenter(ctx: Context): HogePresenter {…} } class FugaActivityScopeContainer { fun resolveFugaPresenter(ctx: Context): FugaPresenter {…} } Singletonͳ ΫϥεͷΈ
object Container { // লུ val userRepo = UserRepository(apiClient, logger)
} class HogeActivityScopeContainer { fun resolveHogePresenter(ctx: Context): HogePresenter {…} } class FugaActivityScopeContainer { fun resolveFugaPresenter(ctx: Context): FugaPresenter {…} } HogeActivityͱಉ͡ είʔϓͷΫϥεΛฦ͢
// DIΛಋೖޙͷίʔυ class HogeActivity : AppCompatActivity() { private val container
= HogeActivityScopeContainer() private val presenter: HogePresenter by lazy { container.resolveHogePresenter(applicationContext) } override fun onCreate(savedInstanceState: Bundle?) { // লུ }ɹ fun onButtonClick(view: View) { presenter.onButtonClick() } } ͜ͷActivityͷ ίϯςφΛॳظԽ
// DIΛಋೖޙͷίʔυ class HogeActivity : AppCompatActivity() { private val container
= HogeActivityScopeContainer() private val presenter: HogePresenter by lazy { container.resolveHogePresenter(applicationContext) } override fun onCreate(savedInstanceState: Bundle?) { // লུ }ɹ fun onButtonClick(view: View) { presenter.onButtonClick() } } containerΛͬͯೖ
͜͜·Ͱͷ·ͱΊ • DIͱίϯϙʔωϯτؒͷґଘؔΛ֎෦ʹ͍ग़͢ύλʔϯ • ґଘίϯϙʔωϯτΛ֎෦͔Βड͚औΔΑ͏ʹ͢Δ • είʔϓΦϒδΣΫτͷੜଘظؒΛද͠ɺείʔϓ͝ͱʹίϯςφ Λ࣋ͭ • ͜͜·Ͱͷ࣮͋͘·Ͱղઆ༻ͷࡶͳͷͳͷͰҙʂ
ΑΓ࣮ફతͳ࣮ํ๏࣍Ҏ߱
DIίϯςφ
DIίϯςφ • DIΛ࣮ݱ͢ΔͨΊͷϥΠϒϥϦ • ઌఔࣗͰ࣮ͨ͠ContainerʢΑΓͬͱ͍͍ͷʣΛࣗಈ Ͱ࡞ͬͯ͘ΕΔϥΠϒϥϦ
AndroidͰ༗໊ͳDIίϯςφ • Dagger2 • Koin • Kodein • Toothpick •
Roboguice
AndroidͰ༗໊ͳDIίϯςφ • Dagger2 • Koin • Kodein • Toothpick •
Roboguice
Dagger2 https://google.github.io/dagger/
Dagger2 • ݩʑSquare͕։ൃ => ݱࡏGoogle͕fork • Annotation processorΛ༻ • ίϯύΠϧ࣌ʹґଘπϦʔʹ͕͋Δͱ͔Δ"
• ίϯετϥΫλ͕͑ΔΫϥεͷ߹ɺґଘؔͷղܾΛࣗಈੜͰ͖Δ" • Ϗϧυ͘ͳΔ# • υΩϡϝϯτ͕͍͠###
ҙʂ • Dagger2͔ͳΓଟػೳͰ͕͢ɺ࣌ؒͷ্ؔ͋ͬ͞Γ͔͠հͰ͖· ͤΜ • Dagger2ʹAndroidʹݶΒͳ͍௨ৗ൛ͱAndroidαϙʔτ൛ ʢdagger.androidʣ͕͋Δ • ࠓdagger.androidͰͷActivityͷೖΛհ •
ʮ͓·͡ͳ͍ʯ͕େྔʹग़ͯ͘ΔͷͰҙ
Dagger2ͷొਓ • Module • Component
Module • ֤ґଘΫϥεΛͲ͏ΠϯελϯεԽ͢Δ͔Λఆٛ͢ΔΫϥε • Daggerͷ߹ίϯετϥΫλΛͬͯΠϯελϯεԽ͢Δ ΫϥεࣗಈతʹੜͰ͖Δɻ ίϯετϥΫλ͕͑ͳ͍ΫϥεΛͲ͏ͬͯΠϯελϯεԽ ͢Δ͔͚ͩఆٛ͢ΕΑ͍ɻ
Α͋͘ΔMVPͷྫ HogeActivity HogePresenter UserRepository ApiClient UserDao
class ApiClientImpl private constructor(): ApiClient { class Builder { fun
build(): ApiClient {...} } override fun getUser(id: Long): Single<User> {...} } class UserDaoImpl(): UserDao { override fun find(id: Long): Single<User> {...} } class UserRepository( private val apiClient: ApiClient, private val userDao: UserDao ) { fun find(id: Long): Single<Hoge> {...} }
class ApiClientImpl private constructor(): ApiClient { class Builder { fun
build(): ApiClient {...} } override fun getUser(id: Long): Single<User> {...} } class UserDaoImpl(): UserDao { override fun find(id: Long): Single<User> {...} } class UserRepository( private val apiClient: ApiClient, private val userDao: UserDao ) { fun find(id: Long): Single<Hoge> {...} } ࣗಈੜͰ͖Δ" =>Moduleʹॻ͘ඞཁͳ͠ ࣗಈੜͰ͖ͳ͍# => Moduleʹॻ͘ඞཁ͋Γ
@Module class AppModule { @Singleton @Provides fun provideApiClient(): ApiClient {
return ApiClientImpl.Builder().build() } }
@Module class AppModule { @Singleton @Provides fun provideApiClient(): ApiClient {
return ApiClientImpl.Builder().build() } } Dagger2ͷϞδϡʔϧΛҙຯ͢Δ
@Module class AppModule { @Singleton @Provides fun provideApiClient(): ApiClient {
return ApiClientImpl.Builder().build() } }
@Module class AppModule { @Singleton @Provides fun provideApiClient(): ApiClient {
return ApiClientImpl.Builder().build() } } ͜ͷϝιουΛґଘղܾʹ͏
@Module class AppModule { @Singleton @Provides fun provideApiClient(): ApiClient {
return ApiClientImpl.Builder().build() } } ApiClientͷείʔϓ
class ApiClientImpl private constructor(): ApiClient { class Builder { fun
build(): ApiClient {...} } override fun getUser(id: Long): Single<User> {...} } class UserDaoImpl(): UserDao { override fun find(id: Long): Single<User> {...} } class UserRepository( private val apiClient: ApiClient, private val userDao: UserDao ) { fun find(id: Long): Single<Hoge> {...} } ࣗಈੜͰ͖Δ" =>Moduleʹॻ͘ඞཁͳ͠ ࣗಈੜͰ͖ͳ͍# => Moduleʹॻ͘ඞཁ͋Γ
@Singleton class UserRepository @Inject constructor( private val apiClient: ApiClient, private
val userDao: UserDao ) { fun find(id: Long): Single<User> {...} } ͜ͷίϯετϥΫλΛͬͯΠϯελϯεԽ͞ΕΔ
@Module abstract class ActivityModule { @ActivityScope @ContributesAndroidInjector abstract fun contributeHogeActivity():
HogeActivity } dagger.androidΛ͏ͨΊͷಛघͳϞδϡʔϧ @ProvidesϝιουΛ࣋ͨͳ͍
@Module abstract class ActivityModule { @ActivityScope @ContributesAndroidInjector abstract fun contributeHogeActivity():
HogeActivity } HogeActivityʹೖ͢ΔͨΊͷϝιου
Component • ModuleΛͬͯґଘ͍ͯ͠ΔΦϒδΣΫτΛੜɾೖ͢Δ Ϋϥε
@Singleton @Component(modules = [ AppModule::class, ActivityModule::class, AndroidInjectionModule::class ]) interface AppComponent:
AndroidInjector<App> { @Component.Builder abstract class Builder: AndroidInjector.Builder<App>() }
@Singleton @Component(modules = [ AppModule::class, ActivityModule::class, AndroidInjectionModule::class ]) interface AppComponent:
AndroidInjector<App> { @Component.Builder abstract class Builder: AndroidInjector.Builder<App>() } ͜ͷίϯϙʔωϯτ͕͏ Ϟδϡʔϧͷྻ
@Singleton @Component(modules = [ AppModule::class, ActivityModule::class, AndroidInjectionModule::class ]) interface AppComponent:
AndroidInjector<App> { @Component.Builder abstract class Builder: AndroidInjector.Builder<App>() } Android support༻ͷΠϯλϑΣʔεΛܧঝͤ͞Δ
class App : Application(), HasActivityInjector { @Inject lateinit var injector:
DispatchingAndroidInjector<Activity> override fun onCreate() { super.onCreate() // Dagger2ͷॳظԽ DaggerAppComponent.builder() .create(this) .inject(this) } override fun activityInjector(): AndroidInjector<Activity> = injector }
class App : Application(), HasActivityInjector { @Inject lateinit var injector:
DispatchingAndroidInjector<Activity> override fun onCreate() { super.onCreate() // Dagger2ͷॳظԽ DaggerAppComponent.builder() .create(this) .inject(this) } override fun activityInjector(): AndroidInjector<Activity> = injector } Android support༻ͷΠϯλϑΣʔεΛܧঝͤ͞Δ
class App : Application(), HasActivityInjector { @Inject lateinit var injector:
DispatchingAndroidInjector<Activity> override fun onCreate() { super.onCreate() // Dagger2ͷॳظԽ DaggerAppComponent.builder() .create(this) .inject(this) } override fun activityInjector(): AndroidInjector<Activity> = injector } AppʹinjectorΛೖ
class HogeActivity : AppCompatActivity() { @Inject lateinit var presenter: HogePresenter
override fun onCreate(savedInstanceState: Bundle?) { // লུ AndroidInjection.inject(this) } fun onButtonClick(view: View) { presenter.onButtonClick() } }
class HogeActivity : AppCompatActivity() { @Inject lateinit var presenter: HogePresenter
override fun onCreate(savedInstanceState: Bundle?) { // লུ AndroidInjection.inject(this) } fun onButtonClick(view: View) { presenter.onButtonClick() } } ґଘΦϒδΣΫτʹ @InjectΛ͚͓ͯ͘
class HogeActivity : AppCompatActivity() { @Inject lateinit var presenter: HogePresenter
override fun onCreate(savedInstanceState: Bundle?) { // লུ AndroidInjection.inject(this) } fun onButtonClick(view: View) { presenter.onButtonClick() } } ґଘΦϒδΣΫτΛೖ
ͳΔ΄ͲɺΘ͔ΒΜ • େৎ • ଟΈΜͳ࠷ॳ͔ͬͯͳ͍ • ࠓޙDaggerΛಋೖ͢Δͱ͖ʹɺ͏Ұ͜ͷࢿྉଞͷαϯϓ ϧΛݟͤOK
Koin https://insert-koin.io/
Koin • KotlinͰॻ͔ΕͨDIίϯςφ • ҠৡϓϩύςΟΛ༻ͯ͠ґଘੑΛղܾ • Annotation processorෆ༻ • ϏϧυʹѱӨڹͳ͍"
• ࣮ߦ͢Δ·ͰґଘπϦʔʹ͕ͳ͍͔͔Βͳ͍# • constructor injectionͰ͖ΔΫϥεͰɺϞδϡʔϧʹॻ͘ඞཁ͋Γ# • υΩϡϝϯτ͕Θ͔Γ͍͢" • AAC ViewModelʹରԠ"
͍ํ 1. moduleͷఆٛ • ͜͜Dagger2ͱಉ͡ 2. startKoinͰίϯςφ࡞ 3. ೖઌͷΫϥεͰҠৡϓϩύςΟΛͬͯೖ͢Δ
class App : Application() { override fun onCreate() { super.onCreate()
// KoinͷϞδϡʔϧͷઃఆ val appModule = module { single<Logger> { LoggerImpl() } factory<ApiClient> { ApiClientImpl.Builder().build() } factory<HogeRepository> { HogeRepository(get()) } } // ґଘπϦʔͷ࡞ startKoin(this, listOf(appModule)) } }
class App : Application() { override fun onCreate() { super.onCreate()
// KoinͷϞδϡʔϧͷઃఆ val appModule = module { single<Logger> { LoggerImpl() } factory<ApiClient> { ApiClientImpl.Builder().build() } factory<HogeRepository> { HogeRepository(get()) } } // ґଘπϦʔͷ࡞ startKoin(this, listOf(appModule)) } } Ϟδϡʔϧͷ࡞
class App : Application() { override fun onCreate() { super.onCreate()
// KoinͷϞδϡʔϧͷઃఆ val appModule = module { single<Logger> { LoggerImpl() } factory<ApiClient> { ApiClientImpl.Builder().build() } factory<HogeRepository> { HogeRepository(get()) } } // ґଘπϦʔͷ࡞ startKoin(this, listOf(appModule)) } } Singleton
class App : Application() { override fun onCreate() { super.onCreate()
// KoinͷϞδϡʔϧͷઃఆ val appModule = module { single<Logger> { LoggerImpl() } factory<ApiClient> { ApiClientImpl.Builder().build() } factory<HogeRepository> { HogeRepository(get()) } } // ґଘπϦʔͷ࡞ startKoin(this, listOf(appModule)) } } ΦϒδΣΫτΛ ຖճ࡞͢ΔΫϥε
class App : Application() { override fun onCreate() { super.onCreate()
// KoinͷϞδϡʔϧͷઃఆ val appModule = module { single<Logger> { LoggerImpl() } factory<ApiClient> { ApiClientImpl.Builder().build() } factory<HogeRepository> { HogeRepository(get()) } } // ґଘπϦʔͷ࡞ startKoin(this, listOf(appModule)) } } getͰґଘղܾ
class App : Application() { override fun onCreate() { super.onCreate()
// KoinͷϞδϡʔϧͷઃఆ val appModule = module { single<Logger> { LoggerImpl() } factory<ApiClient> { ApiClientImpl.Builder().build() } factory<HogeRepository> { HogeRepository(get()) } } // ґଘπϦʔͷ࡞ startKoin(this, listOf(appModule)) } } ίϯςφͷ࡞
class HogeActivity : AppCompatActivity() { val logger: Logger by inject()
override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) // ... logger.v(“Hello world!”) } } ҕৡϓϩύςΟΛͬͯೖ
AAC ViewModelʹରԠ • ViewModelFactoryܦ༝ͰΠϯελϯεΛੜ͢Δ • FactoryΛܧঝ͢ΔΈ͕͋ΓɺDIͱΈ߹ΘͤΔͷ τϦοΫ͕ඞཁ • KoinରԠ͍ͯ͠Δʂ
class HogeViewModel( private val userRepository: UserRepository ) : ViewModel() {…}
class App : Application() { override fun onCreate() { super.onCreate()
// KoinͷϞδϡʔϧͷઃఆ val appModule = module { // ... viewModel { HogeViewModel(get()) } } startKoin(this, listOf(appModule)) } } ViewModelͷΠϯελϯεԽ
class HogeActivity : AppCompatActivity() { val hogeViewModel: HogeViewModel by viewModel()
override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) // ... } fun onButtonClick(view: View) { hogeViewModel.onButtonClick() } } ViewModel༻ͷ ҕৡϓϩύςΟΛ͏
͜͜·Ͱͷ·ͱΊ • DIίϯςφ = DIΛ؆୯ʹ࣮͢ΔͨΊͷϥΠϒϥϦ • Dagger2…ଟػೳ͕͍ͩ͠ɻconstructor injectionͷ߹ ϞδϡʔϧলུՄೳɻ •
Koin…Θ͔Γ͍͢ɻAAC ViewModelʹରԠ͍ͯ͠Δɻ
DIΛಋೖͯ͠Կ͕มΘͬͨʁ
DIͷϝϦοτʢ࠶ܝʣ • ίϯϙʔωϯτ͕ؒૄ݁߹ʹͳΔ͜ͱͰɺҎԼͷϝϦοτ͕͋ Δ • ΞϓϦέʔγϣϯΛ֦ு͘͢͠ͳΔ • ςετ͕ॻ͖͘͢ͳΔ • ͜ΕޙͰৄ͘͠ղઆ
୯ମςετ
// ςετରͷΫϥεʢDIಋೖલʣ class UserRepository { private val apiClient: ApiClient =
ApiClientImpl() fun find(id: Long): Single<User> = apiClient.getUser(id) } ApiClientʹີ݁߹͍ͯ͠Δ
// UserRepositoryͷςετίʔυ class UserRepositoryTest { @Test fun find_isSuccess() { val
repo = UserRepository() repo.find(1) .test() .awaitCount(1) .assertValue(expected) } }
// UserRepositoryͷςετίʔυ class UserRepositoryTest { @Test fun find_isSuccess() { val
repo = UserRepository() repo.find(1) .test() .awaitCount(1) .assertValue(expected) } } ApiClientImplͷ ίʔυ࣮ߦ͍ͯ͠Δ
HogeActivity HogeViewModel UserRepository ApiClient ͕͜͜ςετରʹ ͳͬͯ͠·͍ͬͯΔ
HogeActivity HogeViewModel UserRepository ApiClient ຊʹςετ͍ͨ͠ͷ ͚ͩ͜͜
DIΛಋೖ͍ͯ͠Δ߹ • ґଘίϯϙʔωϯτΛೖ͢Δͱ͖ʹɺςετʹ߹ͷ͍͍ ΦϒδΣΫτΛ͏͜ͱͰɺରͷΫϥεͷΈݕূՄೳ • Mockk, MockitoͳͲͰϞοΫʹ͢Δ͜ͱ͕ଟ͍
// ςετରͷΫϥεʢDIಋೖޙʣ class UserRepository( private val apiClient: ApiClient ) {
fun find(id: Long): Single<User> = apiClient.getUser(id) } ApiClientΛ ֎෦͔ΒೖՄೳ
// UserRepositoryͷςετίʔυ class UserRepositoryTest { @Test fun find_isSuccess() { val
apiClient = mockk<ApiClient> { every { getUser(any()) } returns Single.just(user) } val repo = UserRepository(apiClient) repo.find(1) .test() .awaitCount(1) .assertValue(user) } }
// UserRepositoryͷςετίʔυ class UserRepositoryTest { @Test fun find_isSuccess() { val
apiClient = mockk<ApiClient> { every { getUser(any()) } returns Single.just(user) } val repo = UserRepository(apiClient) repo.find(1) .test() .awaitCount(1) .assertValue(user) } } ApiClientͷϞοΫΛ࡞
// UserRepositoryͷςετίʔυ class UserRepositoryTest { @Test fun find_isSuccess() { val
apiClient = mockk<ApiClient> { every { getUser(any()) } returns Single.just(user) } val repo = UserRepository(apiClient) repo.find(1) .test() .awaitCount(1) .assertValue(user) } } ϞοΫΛೖͯ͠ ςετ࣮ߦ
݁߹ςετ
݁߹ςετ • ???ʮͲ͏ͤ݁߹ͨ͠ঢ়ଶͰςετ͢Δ͔ΒDIͰίϯϙʔωϯ τؒΛૄ݁߹ʹͯ͠ҙຯͳ͍Μ͡Όͳ͍Μͷʁʯ • ͦΜͳ͜ͱͳ͍
ྫ: EspressoͰUIςετΛॻ͘ • APIΫϥΠΞϯτͷԠ͕ຖճҧ͏ͷͰਏ͍ • Ϩεϙϯε͕࣌ؒʹΑͬͯҧ͏ • Ԡ࣌ؒ • Α͘API͕յΕΔʢ։ൃڥͳͲʣ
• DIΛಋೖ͍ͯ͠Δͱɺ؆୯ʹAPIΫϥΠΞϯτΛςετ༻ͷϞοΫʹࠩ͠ସ͑ Δ͜ͱ͕Ͱ͖Δ
DIίϯςφͷϞδϡʔϧΛςετ༻ʹ ࠩ͠ସ͑Δ 1. ςετ༻ͷApplicationΛ࡞͠ɺ෦Ͱςετ༻Ϟδϡʔϧ ΛͬͯґଘπϦʔΛ࡞Δ 2. Instrumented test࣮ߦ࣌ʹىಈ͢ΔApplicationΫϥεΛɺ 1Ͱ࡞ͬͨΫϥεʹࠩ͠ସ͑Δ
open class App : Application() { private val appModule =
module { single<Logger> { LoggerImpl() } factory { UserRepository(get()) } viewModel { MainViewModel(get(), get()) } } // APIΫϥΠΞϯτ༻Ϟδϡʔϧ protected open val apiModule = module { factory<ApiClient> { ApiClientImpl() } } override fun onCreate() { super.onCreate() // ґଘπϦʔΛߏங startKoin(this, listOf(appModule, apiModule)) } }
open class App : Application() { private val appModule =
module { single<Logger> { LoggerImpl() } factory { UserRepository(get()) } viewModel { MainViewModel(get(), get()) } } // APIΫϥΠΞϯτ༻Ϟδϡʔϧ protected open val apiModule = module { factory<ApiClient> { ApiClientImpl() } } override fun onCreate() { super.onCreate() // ґଘπϦʔΛߏங startKoin(this, listOf(appModule, apiModule)) } } ςετ༻ͷAppΫϥεΛ ܧঝͯ͠࡞ΕΔΑ͏ʹ͢Δ
open class App : Application() { private val appModule =
module { single<Logger> { LoggerImpl() } factory { UserRepository(get()) } viewModel { MainViewModel(get(), get()) } } // APIΫϥΠΞϯτ༻Ϟδϡʔϧ protected open val apiModule = module { factory<ApiClient> { ApiClientImpl() } } override fun onCreate() { super.onCreate() // ґଘπϦʔΛߏங startKoin(this, listOf(appModule, apiModule)) } } ͜͜ΦʔόʔϥΠυ Ͱ͖ΔΑ͏ʹ͢Δ
class TestApp : App() { override val apiModule = module
{ factory<ApiClient> { val user = User(123, “kobakei”) val apiClient = mockk<ApiClient> { every { getUser(any()) } returns Single.just(user) } apiClient } } }
class TestApp : App() { override val apiModule = module
{ factory<ApiClient> { val user = User(123, “kobakei”) val apiClient = mockk<ApiClient> { every { getUser(any()) } returns Single.just(user) } apiClient } } } ϞοΫͷAPIΫϥΠΞϯτΛฦ͢ ϞδϡʔϧͰΦʔόʔϥΠυ
// ΞϓϦέʔγϣϯΛࠩ͠ସ͑ΔͨΊͷϥϯφʔ class MyTestRunner : AndroidJUnitRunner() { override fun newApplication(cl:
ClassLoader?, name: String?, c: Context?) = super.newApplication(cl, TestApp::class.java.name, c) } TestAppΫϥεΛىಈ͢Δ
// app/build.gradle android { defaultConfig { testInstrumentationRunner “your.app.MyTestRunner” } }
Instrumented test࣌ͷϥϯφʔΛࢦఆ
ຊͷ·ͱΊ
·ͱΊ • DI = ίϯϙʔωϯτؒͷґଘؔΛ֎෦ʹ͍ग़͢σβΠϯύ λʔϯ • DIΛಋೖ͢Δ͜ͱͰɺίϯϙʔωϯτ͕ૄ݁߹ʹͳΓɺίϯ ϙʔωϯτΛࠩ͠ସ͑ͨΓɺςετ͕ॻ͖͘͢ͳΔ •
Dagger2KoinͳͲDIίϯςφΛར༻͠Α͏
Thanks!!!