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
今日から始める依存性の注入 / First Time Dependency Injection
Search
Sponsored
·
Ship Features Fearlessly
Turn features on and off without deploys. Used by thousands of Ruby developers.
→
Keisuke Kobayashi
February 08, 2019
Programming
7.7k
26
Share
今日から始める依存性の注入 / First Time Dependency Injection
DroidKaigi 2019
Room1, 2019/02/08 14:50~15:20
Keisuke Kobayashi
February 08, 2019
More Decks by Keisuke Kobayashi
See All by Keisuke Kobayashi
AI 1st でエンタープライズ SaaS を立ち上げる / AI 1st Enterprise SaaS
kobakei
1
50
プロダクト開発をAI 1stに変革する〜SaaS is dead時代で生き残るために〜 / AI 1st Product Development
kobakei
0
2.5k
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
340
APIクライアントをCodableで置き換えた話
kobakei
0
1.6k
開発者が知っておきたい通知の歴史
kobakei
9
7.8k
mockito-kotlin
kobakei
1
540
Other Decks in Programming
See All in Programming
AIと共に生きる技術選定 2026
sgash708
0
110
Kingdom of the Machine
yui_knk
2
870
セグメントとターゲットを意識するプロポーザルの書き方 〜採択の鍵は、誰に刺すかを見極めるマーケティング戦略にある〜
m3m0r7
PRO
0
580
Swift Concurrency Type System
inamiy
1
540
AI時代のPhpStorm最新事情 #phpcon_odawara
yusuke
0
200
[RubyKaigi 2026] Require Hooks
palkan
1
230
感情を設計する
ichimichi
5
1.6k
t *testing.T は どこからやってくるの?
otakakot
1
710
ソフトウェア設計の結合バランス #phperkaigi
kajitack
0
140
tRPCの概要と少しだけパフォーマンス
misoton665
2
230
의존성 주입과 모듈화
fornewid
0
150
Kubernetes上でAgentを動かすための最新動向と押さえるべき概念まとめ
sotamaki0421
3
650
Featured
See All Featured
My Coaching Mixtape
mlcsv
0
110
Organizational Design Perspectives: An Ontology of Organizational Design Elements
kimpetersen
PRO
1
680
Data-driven link building: lessons from a $708K investment (BrightonSEO talk)
szymonslowik
1
1k
Principles of Awesome APIs and How to Build Them.
keavy
128
17k
How to build an LLM SEO readiness audit: a practical framework
nmsamuel
1
720
Jess Joyce - The Pitfalls of Following Frameworks
techseoconnect
PRO
1
140
Digital Ethics as a Driver of Design Innovation
axbom
PRO
1
270
Responsive Adventures: Dirty Tricks From The Dark Corners of Front-End
smashingmag
254
22k
Site-Speed That Sticks
csswizardry
13
1.2k
GraphQLとの向き合い方2022年版
quramy
50
15k
Refactoring Trust on Your Teams (GOTO; Chicago 2020)
rmw
35
3.4k
コードの90%をAIが書く世界で何が待っているのか / What awaits us in a world where 90% of the code is written by AI
rkaga
61
43k
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!!!