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.3k
今日から始める依存性の注入 / 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
920
iOSアプリ内で不正なSSL証明書を検知する / SSL Pinning for iOS apps
kobakei
34
11k
Kyashアプリ開発の現場
kobakei
4
2.7k
Review of Google I/O 2017 & Prepare for Google I/O 2018
kobakei
0
310
APIクライアントをCodableで置き換えた話
kobakei
0
1.5k
開発者が知っておきたい通知の歴史
kobakei
9
7.4k
mockito-kotlin
kobakei
1
510
2017年に新規アプリを立ち上げた話
kobakei
2
1.1k
Everything of CI/CD in Kyash Android
kobakei
0
1.6k
Other Decks in Programming
See All in Programming
PHPで学ぶプログラミングの教訓 / Lessons in Programming Learned through PHP
nrslib
2
260
rails stats で紐解く ANDPAD のイマを支える技術たち
andpad
1
290
Amazon S3 NYJavaSIG 2024-12-12
sullis
0
100
Effective Signals in Angular 19+: Rules and Helpers @ngbe2024
manfredsteyer
PRO
0
140
今年のアップデートで振り返るCDKセキュリティのシフトレフト/2024-cdk-security-shift-left
tomoki10
0
200
return文におけるstd::moveについて
onihusube
1
1.1k
103 Early Hints
sugi_0000
1
230
情報漏洩させないための設計
kubotak
2
170
バグを見つけた?それAppleに直してもらおう!
uetyo
0
180
DevFest Tokyo 2025 - Flutter のアプリアーキテクチャ現在地点
wasabeef
5
910
range over funcの使い道と非同期N+1リゾルバーの夢 / about a range over func
mackee
0
110
これでLambdaが不要に?!Step FunctionsのJSONata対応について
iwatatomoya
2
3.7k
Featured
See All Featured
Facilitating Awesome Meetings
lara
50
6.1k
Fontdeck: Realign not Redesign
paulrobertlloyd
82
5.3k
The Power of CSS Pseudo Elements
geoffreycrofte
73
5.4k
Build The Right Thing And Hit Your Dates
maggiecrowley
33
2.4k
Refactoring Trust on Your Teams (GOTO; Chicago 2020)
rmw
32
2.7k
ReactJS: Keep Simple. Everything can be a component!
pedronauck
665
120k
Mobile First: as difficult as doing things right
swwweet
222
9k
Templates, Plugins, & Blocks: Oh My! Creating the theme that thinks of everything
marktimemedia
28
2.1k
Creating an realtime collaboration tool: Agile Flush - .NET Oxford
marcduiker
26
1.9k
4 Signs Your Business is Dying
shpigford
181
21k
Art, The Web, and Tiny UX
lynnandtonic
298
20k
Bootstrapping a Software Product
garrettdimon
PRO
305
110k
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!!!