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
Keisuke Kobayashi
February 08, 2019
Programming
26
7.4k
今日から始める依存性の注入 / 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
iOSアプリの技術的負債をどう返済したか / How to repay the technical debt of iOS app
kobakei
2
940
iOSアプリ内で不正なSSL証明書を検知する / SSL Pinning for iOS apps
kobakei
34
11k
Kyashアプリ開発の現場
kobakei
4
2.8k
Review of Google I/O 2017 & Prepare for Google I/O 2018
kobakei
0
310
APIクライアントをCodableで置き換えた話
kobakei
0
1.5k
開発者が知っておきたい通知の歴史
kobakei
9
7.5k
mockito-kotlin
kobakei
1
520
2017年に新規アプリを立ち上げた話
kobakei
2
1.1k
Everything of CI/CD in Kyash Android
kobakei
0
1.6k
Other Decks in Programming
See All in Programming
The Evolution of Enterprise Java with Jakarta EE 11 and Beyond
ivargrimstad
0
960
Let's Take a Peek at PHP Parser 5.x!
inouehi
0
100
snacks.nvim内のセットアップ不要なプラグインを紹介 / introduce_snacks_nvim
uhooi
0
340
SQL Server ベクトル検索
odashinsuke
0
120
PHPer's Guide to Daemon Crafting Taming and Summoning
uzulla
2
1.1k
Day0 初心者向けワークショップ実践!ソフトウェアテストの第一歩
satohiroyuki
0
420
Kubernetesで実現できるPlatform Engineering の現在地
nwiizo
2
1.7k
PHPUnit 高速化テクニック / PHPUnit Speedup Techniques
pinkumohikan
1
1.2k
コンテナでLambdaをデプロイするときに知っておきたかったこと
_takahash
0
150
データベースエンジニアの仕事を楽にする。PgAssistantの紹介
nnaka2992
9
4.2k
本当だってば!俺もTRICK 2022に入賞してたんだってば!
jinroq
0
250
ニックトレイン登壇資料
ryotakurokawa
0
140
Featured
See All Featured
Faster Mobile Websites
deanohume
306
31k
Sharpening the Axe: The Primacy of Toolmaking
bcantrill
40
2k
個人開発の失敗を避けるイケてる考え方 / tips for indie hackers
panda_program
102
18k
Designing for humans not robots
tammielis
250
25k
Mobile First: as difficult as doing things right
swwweet
223
9.5k
Learning to Love Humans: Emotional Interface Design
aarron
273
40k
The Pragmatic Product Professional
lauravandoore
33
6.5k
The Illustrated Children's Guide to Kubernetes
chrisshort
48
49k
Agile that works and the tools we love
rasmusluckow
328
21k
Measuring & Analyzing Core Web Vitals
bluesmoon
6
320
Visualizing Your Data: Incorporating Mongo into Loggly Infrastructure
mongodb
45
9.5k
How to Ace a Technical Interview
jacobian
276
23k
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!!!