$30 off During Our Annual Pro Sale. View Details »

今日から始める依存性の注入 / First Time Dependency Injection

今日から始める依存性の注入 / First Time Dependency Injection

DroidKaigi 2019
Room1, 2019/02/08 14:50~15:20

Keisuke Kobayashi

February 08, 2019
Tweet

More Decks by Keisuke Kobayashi

Other Decks in Programming

Transcript

  1. ࣗݾ঺հ • Keisuke Kobayashi • Twitter: @kobakei122 • GitHub: @kobakei

    • Merpay, Inc. / Engineering Manager • Studyplus, Inc. / ٕज़ސ໰ʢ෭ۀʣ
  2. // 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)...
  3. // 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)... ґଘΦϒδΣΫτͱ
 ີ݁߹͍ͯ͠Δ
  4. // 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)...
  5. // 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)... ίϯετϥΫλͰ
 ґଘΦϒδΣΫτΛ ड͚औΔ
  6. // 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)... ࢖͏ଆ͕ґଘΦϒδΣΫτ͔Β
 ૊Έཱ͍ͯͯΔ
  7. // ґଘΦϒδΣΫτΛղܾ͢ΔͨΊͷίϯςφΦϒδΣΫτ object Container { val apiClient: ApiClient = ApiClientImpl()

    val userDao: UserDao = UserDaoImpl() val userRepo: UserRepository = UserRepository(apiClient, userDao) }
  8. // 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ͷґଘؔ܎ ͕ഉআ͞Εͨ
  9. // DIΛಋೖ͍ͯ͠ͳ͍ίʔυ class HogeActivity : AppCompatActivity() { private val presenter

    = HogePresenter() override fun onCreate(savedInstanceState: Bundle?) { // লུ } fun onButtonClick(view: View) { presenter.onButtonClick() } } Activity͕ HogePresenterʹ
 ີ݁߹͍ͯ͠Δ
  10. // DIΛಋೖ͍ͯ͠ͳ͍ίʔυ class HogeActivity : AppCompatActivity() { private lateinit var

    presenter: HogePresenter override fun onCreate(savedInstanceState: Bundle?) { // লུ presenter = HogePresenter(applicationContext) } fun onButtonClick(view: View) { presenter.onButtonClick() } } ͜Ε΋ີ݁߹͍ͯ͠Δ
  11. // ґଘΦϒδΣΫτΛղܾ͢ΔͨΊͷίϯςφΦϒδΣΫτ 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ΛҾ਺ʹऔΔϝιουΛ௥Ճ
  12. // 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Λ౉ͤΔ
  13. object Container { // লུ val userRepo = UserRepository(apiClient, logger)

    } class HogeActivityScopeContainer { fun resolveHogePresenter(ctx: Context): HogePresenter {…} } class FugaActivityScopeContainer { fun resolveFugaPresenter(ctx: Context): FugaPresenter {…} }
  14. object Container { // লུ val userRepo = UserRepository(apiClient, logger)

    } class HogeActivityScopeContainer { fun resolveHogePresenter(ctx: Context): HogePresenter {…} } class FugaActivityScopeContainer { fun resolveFugaPresenter(ctx: Context): FugaPresenter {…} } Singletonͳ ΫϥεͷΈ
  15. object Container { // লུ val userRepo = UserRepository(apiClient, logger)

    } class HogeActivityScopeContainer { fun resolveHogePresenter(ctx: Context): HogePresenter {…} } class FugaActivityScopeContainer { fun resolveFugaPresenter(ctx: Context): FugaPresenter {…} } HogeActivityͱಉ͡
 είʔϓͷΫϥεΛฦ͢
  16. // 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ͷ ίϯςφΛॳظԽ
  17. // 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Λ࢖ͬͯ஫ೖ
  18. Dagger2 • ݩʑSquare͕։ൃ => ݱࡏ͸Google͕fork • Annotation processorΛ࢖༻ • ίϯύΠϧ࣌ʹґଘπϦʔʹ໰୊͕͋Δͱ෼͔Δ"

    • ίϯετϥΫλ͕࢖͑ΔΫϥεͷ৔߹ɺґଘؔ܎ͷղܾΛࣗಈੜ੒Ͱ͖Δ" • Ϗϧυ͸஗͘ͳΔ# • υΩϡϝϯτ͕೉͍͠###
  19. 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> {...} }
  20. 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ʹॻ͘ඞཁ͋Γ
  21. @Module class AppModule { @Singleton @Provides fun provideApiClient(): ApiClient {

    return ApiClientImpl.Builder().build() } } Dagger2ͷϞδϡʔϧΛҙຯ͢Δ
  22. @Module class AppModule { @Singleton @Provides fun provideApiClient(): ApiClient {

    return ApiClientImpl.Builder().build() } } ͜ͷϝιουΛґଘղܾʹ࢖͏
  23. @Module class AppModule { @Singleton @Provides fun provideApiClient(): ApiClient {

    return ApiClientImpl.Builder().build() } } ApiClientͷείʔϓ
  24. 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ʹॻ͘ඞཁ͋Γ
  25. @Singleton class UserRepository @Inject constructor( private val apiClient: ApiClient, private

    val userDao: UserDao ) { fun find(id: Long): Single<User> {...} } ͜ͷίϯετϥΫλΛ࢖ͬͯΠϯελϯεԽ͞ΕΔ
  26. @Module abstract class ActivityModule { @ActivityScope @ContributesAndroidInjector abstract fun contributeHogeActivity():

    HogeActivity } dagger.androidΛ࢖͏ͨΊͷಛघͳϞδϡʔϧ @ProvidesϝιουΛ࣋ͨͳ͍
  27. @Singleton @Component(modules = [ AppModule::class, ActivityModule::class, AndroidInjectionModule::class ]) interface AppComponent:

    AndroidInjector<App> { @Component.Builder abstract class Builder: AndroidInjector.Builder<App>() }
  28. @Singleton @Component(modules = [ AppModule::class, ActivityModule::class, AndroidInjectionModule::class ]) interface AppComponent:

    AndroidInjector<App> { @Component.Builder abstract class Builder: AndroidInjector.Builder<App>() } ͜ͷίϯϙʔωϯτ͕࢖͏ Ϟδϡʔϧͷ഑ྻ
  29. @Singleton @Component(modules = [ AppModule::class, ActivityModule::class, AndroidInjectionModule::class ]) interface AppComponent:

    AndroidInjector<App> { @Component.Builder abstract class Builder: AndroidInjector.Builder<App>() } Android support༻ͷΠϯλϑΣʔεΛܧঝͤ͞Δ
  30. 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 }
  31. 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༻ͷΠϯλϑΣʔεΛܧঝͤ͞Δ
  32. 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Λ஫ೖ
  33. class HogeActivity : AppCompatActivity() { @Inject lateinit var presenter: HogePresenter

    override fun onCreate(savedInstanceState: Bundle?) { // লུ AndroidInjection.inject(this) } fun onButtonClick(view: View) { presenter.onButtonClick() } }
  34. class HogeActivity : AppCompatActivity() { @Inject lateinit var presenter: HogePresenter

    override fun onCreate(savedInstanceState: Bundle?) { // লུ AndroidInjection.inject(this) } fun onButtonClick(view: View) { presenter.onButtonClick() } } ґଘΦϒδΣΫτʹ͸ @InjectΛ෇͚͓ͯ͘
  35. class HogeActivity : AppCompatActivity() { @Inject lateinit var presenter: HogePresenter

    override fun onCreate(savedInstanceState: Bundle?) { // লུ AndroidInjection.inject(this) } fun onButtonClick(view: View) { presenter.onButtonClick() } } ґଘΦϒδΣΫτΛ஫ೖ
  36. Koin • KotlinͰॻ͔ΕͨDIίϯςφ • ҠৡϓϩύςΟΛ࢖༻ͯ͠ґଘੑΛղܾ • Annotation processorෆ࢖༻ • Ϗϧυ଎౓ʹѱӨڹ͸ͳ͍"

    • ࣮ߦ͢Δ·ͰґଘπϦʔʹ໰୊͕ͳ͍͔෼͔Βͳ͍# • constructor injectionͰ͖ΔΫϥεͰ΋ɺϞδϡʔϧʹॻ͘ඞཁ͋Γ# • υΩϡϝϯτ͕Θ͔Γ΍͍͢" • AAC ViewModelʹରԠ"
  37. 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)) } }
  38. 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)) } } Ϟδϡʔϧͷ࡞੒
  39. 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
  40. 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)) } } ΦϒδΣΫτΛ ຖճ࡞੒͢ΔΫϥε
  41. 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Ͱґଘղܾ
  42. 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)) } } ίϯςφͷ࡞੒
  43. class HogeActivity : AppCompatActivity() { val logger: Logger by inject()

    override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) // ... logger.v(“Hello world!”) } } ҕৡϓϩύςΟΛ࢖ͬͯ஫ೖ
  44. class App : Application() { override fun onCreate() { super.onCreate()

    // KoinͷϞδϡʔϧͷઃఆ val appModule = module { // ... viewModel { HogeViewModel(get()) } } startKoin(this, listOf(appModule)) } } ViewModelͷΠϯελϯεԽ
  45. class HogeActivity : AppCompatActivity() { val hogeViewModel: HogeViewModel by viewModel()

    override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) // ... } fun onButtonClick(view: View) { hogeViewModel.onButtonClick() } } ViewModel༻ͷ ҕৡϓϩύςΟΛ࢖͏
  46. // ςετର৅ͷΫϥεʢDIಋೖલʣ class UserRepository { private val apiClient: ApiClient =

    ApiClientImpl() fun find(id: Long): Single<User> = apiClient.getUser(id) } ApiClientʹີ݁߹͍ͯ͠Δ
  47. // UserRepositoryͷςετίʔυ class UserRepositoryTest { @Test fun find_isSuccess() { val

    repo = UserRepository() repo.find(1) .test() .awaitCount(1) .assertValue(expected) } }
  48. // UserRepositoryͷςετίʔυ class UserRepositoryTest { @Test fun find_isSuccess() { val

    repo = UserRepository() repo.find(1) .test() .awaitCount(1) .assertValue(expected) } } ApiClientImplͷ
 ίʔυ΋࣮ߦ͍ͯ͠Δ
  49. // ςετର৅ͷΫϥεʢDIಋೖޙʣ class UserRepository( private val apiClient: ApiClient ) {

    fun find(id: Long): Single<User> = apiClient.getUser(id) } ApiClientΛ ֎෦͔Β஫ೖՄೳ
  50. // 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) } }
  51. // 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ͷϞοΫΛ࡞੒
  52. // 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) } } ϞοΫΛ஫ೖͯ͠ ςετ࣮ߦ
  53. 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)) } }
  54. 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ΫϥεΛ
 ܧঝͯ͠࡞ΕΔΑ͏ʹ͢Δ
  55. 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)) } } ͜͜΋ΦʔόʔϥΠυ Ͱ͖ΔΑ͏ʹ͢Δ
  56. 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 } } }
  57. 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ΫϥΠΞϯτΛฦ͢ ϞδϡʔϧͰΦʔόʔϥΠυ
  58. // ΞϓϦέʔγϣϯΛࠩ͠ସ͑ΔͨΊͷϥϯφʔ class MyTestRunner : AndroidJUnitRunner() { override fun newApplication(cl:

    ClassLoader?, name: String?, c: Context?) = super.newApplication(cl, TestApp::class.java.name, c) } TestAppΫϥεΛىಈ͢Δ