1 / 33

Dagger + Android contributions

Dagger + Android contributions. Sigitas Atkočaitis. Why Dagger. private fun setUpInjection() { navigator = Navigator() locationPermissionManager = LocationPermissionManager( this ) val latLngMapper = LatLngMapperImpl() val converter = BitmapDescriptorConverter(resources)

mshields
Download Presentation

Dagger + Android contributions

An Image/Link below is provided (as is) to download presentation Download Policy: Content on the Website is provided to you AS IS for your information and personal use and may not be sold / licensed / shared on other websites without getting consent from its author. Content is provided to you AS IS for your information and personal use only. Download presentation by click this link. While downloading, if for some reason you are not able to download a presentation, the publisher may have deleted the file from their server. During download, if you can't get a presentation, the file might be deleted by the publisher.

E N D

Presentation Transcript


  1. Dagger + Android contributions Sigitas Atkočaitis

  2. Why Dagger

  3. private fun setUpInjection() { navigator = Navigator() locationPermissionManager = LocationPermissionManager(this) val latLngMapper = LatLngMapperImpl() val converter = BitmapDescriptorConverter(resources) val markerGenerator = MarkerGeneratorImpl(latLngMapper, converter) val adapter = MarkerInfoWindowAdapter(activity!! .layoutInflater) val clusterIconProvider = ClusterIconProviderImpl(converter) map = PharmaciesMapHolder(markerGenerator, latLngMapper, adapter, this, clusterIconProvider) val retriever = DependencyRetriever.from(context!!) val service = retriever.getService(LoadPharmaciesService::class.java) val dbHelper = DependencyRetriever.from(context!!).databaseHelper val pharmaciesRepository = PharmaciesRepositoryImpl(dbHelper) val prefRepository = PreferenceRepository(context!!) val expireModel = PharmaciesExpireModelImpl(prefRepository) val pharmaciesModel = LoadPharmaciesModelImpl(service, pharmaciesRepository, expireModel, Schedulers.io()) locationService = DefaultLocationService(context!!) val model = PharmaciesMapModelImpl(locationService!!, pharmaciesModel) val resolver = NetworkErrorResolver( DefaultNetworkErrorMessenger(resources)) presenter = PharmaciesMapPresenterImpl(model, AndroidSchedulers.mainThread(), resolver) }

  4. val storageFactory = PreferenceStorageFactory(); val antennaStorage = storageFactory.create(context, Antenna::class); val amplifierStorage = storageFactory.create(context, Amplifier::class); val attenuationStorage = storageFactory.create(context, Attenuation::class); val antennaRepository = AntennaRepository(antennaStorage); val amplifierRepository = AmplifierRepository(amplifierStorage); val attenuationRepository = AttenuationRepository(attenuationStorage); val bandRegionStorage = storageFactory.create(context, BandRegion::class); val colorRangeStorage = storageFactory.create(context, ColorRange::class); val colorRepository = ColorRepositoryImpl(colorRangeStorage); val audioToneStorage = storageFactory.create(context, AudioTone::class); val audioToneRepository = AudioToneRepository(audioToneStorage); val logDirRepository = SessionDirRepositoryImpl( storageFactory.create(context, String::class)); val tileDirRepository = TilesDirRepository( storageFactory.create(context, String::class)); val formStorage = storageFactory.create(context, SettingsForm::class); val bandRegionHolderStorage = storageFactory.create(context, BandRegionHolder::class); val bandListProvider = DefaultBandListProvider(); val bandRegionRepository = BandRegionListRepositoryImpl(bandRegionStorage, bandRegionHolderStorage, bandListProvider); val settingsFormRepository = SettingsFormRepositoryImpl( antennaRepository, amplifierRepository, attenuationRepository, audioToneRepository, bandRegionRepository, colorRepository, logDirRepository, tileDirRepository, formStorage ); val computationScheduler = Schedulers.computation(); val mainScheduler = AndroidSchedulers.mainThread(); val model = SettingsFragmentModel( bandRegionRepository, antennaRepository, amplifierRepository, attenuationRepository, colorRepository, audioToneRepository, formProvider, settingsFormRepository, computationScheduler ); val calculator = AutoColorRangeCalculatorImpl(); val selectedListStorage = storageFactory.create(context, BandRegionHolder::class); val bandRegionListRepository = BandRegionListRepositoryImpl(bandRegionStorage, selectedListStorage, bandListProvider); return SettingsFragmentPresenter( model, calculator, mainScheduler, updateChecker, SeewaveApplication.getRecorderManager(context).getFileManager(), bandRegionListRepository );

  5. Why Dagger More Testable Code More Readable Code More Reusable Code Reduced Dependency Carrying

  6. Main Dagger parts @Component @SubComponent @Module @ContributesAndroidInjector

  7. @DaggerScope(BaseApplication::class) @Component( modules = [ AndroidSupportInjectionModule::class, AppModule::class, ActivitiesModule::class, SchedulerModule::class, NetworkModule::class, ImageModule::class ] ) interface AppComponent : AndroidInjector<BaseApplication> { @Component.Builder abstract class Builder : AndroidInjector.Builder<BaseApplication>() } open class BaseApplication : DaggerApplication() { override fun applicationInjector(): AndroidInjector<out DaggerApplication> = DaggerAppComponent.builder().create(this) }

  8. @Module abstract class AppModule private constructor() { @Binds abstract fun bindContext(app: BaseApplication): Context @Module companion object { @JvmStatic @Provides fun provideResources(context: Context): Resources = context.resources } }

  9. @Module abstract class SchedulerModule private constructor() { @Module companion object { @JvmStatic @Provides @MainThread fun provideMainScheduler(): Scheduler = AndroidSchedulers.mainThread() @JvmStatic @Provides @ComputationThread fun provideComputationScheduler(): Scheduler = Schedulers.computation() @JvmStatic @Provides @IoThread fun provideIoScheduler(): Scheduler = Schedulers.io() @JvmStatic @Provides @NetworkThread fun provideIoScheduler(): Scheduler = Schedulers.io() } } @Qualifier annotation class NetworkThread

  10. @Module abstract class ImageModule private constructor() { @Binds @Reusable abstract fun bindImageLoader(loader: GlideImageLoader): ImageLoader }

  11. @Module abstract class NetworkModule private constructor() { @Module companion object { ... @JvmStatic @Provides @DaggerScope(BaseApplication::class) fun provideRetrofitClient( @NetworkThread networkScheduler: Scheduler ): Retrofit = Retrofit.Builder() .addCallAdapterFactory( RxJava2CallAdapterFactory.createWithScheduler(networkScheduler) ) .baseUrl(BuildConfig.API_ENDPOINT) .build() } } @Scope annotation class DaggerScope(val value: KClass<*>)

  12. @Module abstract class ActivitiesModule private constructor() { @DaggerScope(MainActivity::class) @ContributesAndroidInjector(modules = [MainActivityModule::class]) abstract fun bindMainActivity(): MainActivity } class MainActivity : DaggerAppCompatActivity(){ ... }

  13. @dagger.Module abstract class MainActivityModule private constructor() { @DaggerScope(MainFragment::class) @ContributesAndroidInjector(modules = [MainScreen.Module::class]) abstract fun bindArticleListFragment(): MainFragment }

  14. object MainScreen { interface MainPresenter : BasePresenter<MainScreen.View> {...} interface View {...} @dagger.Module abstract class Module private constructor() { @Binds @DaggerScope(MainFragment::class) abstract fun bindPresenter(presenter: MainPresenterImpl): MainPresenter @dagger.Module companion object { @Provides @JvmStatic @DaggerScope(MainFragment::class) fun provideService(retrofit: Retrofit) = retrofit.create<NewsService>() } } }

  15. class MainPresenterImpl @Inject constructor( private val service: NewsService, @MainThread private val scheduler: Scheduler ) : BasePresenterImpl<MainScreen.View>(), MainScreen.MainPresenter { ... } class MainFragment : DaggerFragment(), MainScreen.View { @Inject lateinit var presenter: MainScreen.MainPresenter override fun onCreateView(...): View? { ... presenter.takeView(this) return fragmentView } ... }

  16. Unit tests You don’t have to use Dagger for single-class unit tests If you want to write a small unit test that tests only one @Inject-annotated class, you don’t need to use Dagger to instantiate that class. If you want to write a traditional unit test, you can directly call the @Inject-annotated constructor and methods and set the @Inject-annotated fields, if any, passing fake or mock dependencies directly, just as you would if they weren’t annotated. https://google.github.io/dagger/testing

  17. class MainPresenterImplTest { private val service = mock<NewsService>() private val view = mock<MainScreen.View>() private val scheduler = TestScheduler() private val presenter = MainPresenterImpl(service, scheduler) private val articles = listOf<Article>(mock()) private val articlesResponse = ArticlesResponse("200", 0, articles) @Test fun exampleUnitTest() { given(service.getArticleList()).willReturn(Single.just(articlesResponse)) presenter.takeView(view) presenter.onViewCreated() scheduler.triggerActions() verify(view).showArticles(articles) } }

  18. Instrumentation testing

  19. @DaggerScope(BaseApplication::class) @Component( modules = [ AndroidSupportInjectionModule::class, InstrumentationModule::class, AppModule::class, ActivitiesModule::class, InstrumentationSchedulerModule::class, NetworkModule::class, ImageModule::class ] ) interface TestAppComponent : AndroidInjector<TestBaseApplication> { @Component.Builder abstract class Builder : AndroidInjector.Builder<TestBaseApplication>() }

  20. open class TestApplicationRunner : AndroidJUnitRunner() { @Throws(Exception::class) override fun newApplication(loader: ClassLoader, className: String, context: Context): Application { return super.newApplication(loader, TestBaseApplication::class.java.name, context) } } class TestBaseApplication : BaseApplication() { override fun applicationInjector(): AndroidInjector<out DaggerApplication> { return DaggerTestAppComponent.builder().create(this) } }

  21. @Module abstract class InstrumentationSchedulerModule private constructor() { @Module companion object { ... @JvmStatic @NetworkThread @Provides fun provideNetworkScheduler(executor: IdlingThreadPoolExecutor): Scheduler = Schedulers.from(executor) @JvmStatic @Provides @DaggerScope(BaseApplication::class) fun provideIdlingThreadPoolExecutor(): IdlingThreadPoolExecutor = IdlingThreadPoolExecutor(...) } }

  22. class ExampleInstrumentedTest { @get:Rule var rule = ActivityTestRule(MainActivity::class.java) @Test fun exampleTest() { onView(withId(R.id.listRecyclerView)).check(matches(hasMinimumChildCount(1))) onView(withId(R.id.listRecyclerView)).perform(ViewActions.swipeUp()) } }

  23. Alternative: Koin.io https://insert-koin.io/

  24. @DaggerScope(BaseApplication::class) @Component( modules = [ AndroidSupportInjectionModule::class, AppModule::class, ActivitiesModule::class, SchedulerModule::class, NetworkModule::class, ImageModule::class ] ) interface AppComponent : AndroidInjector<BaseApplication> { @Component.Builder abstract class Builder : AndroidInjector.Builder<BaseApplication>() }

  25. open class BaseApplication : Application() { override fun onCreate() { startKoin(KoinApplication.create().apply { androidLogger(Level.DEBUG) androidContext(this@BaseApplication) modules( PluginsModule(), AppModule(), SchedulerModule(), NetworkModule(), ImageModule(), MainScreen.Module() ) }) } }

  26. @Module abstract class AppModule private constructor() { @Binds abstract fun bindContext(app: BaseApplication): Context @Module companion object { @JvmStatic @Provides fun provideResources(context: Context): Resources = context.resources } }

  27. fun AppModule() = module { single { androidContext().resources } }

  28. @dagger.Module abstract class Module private constructor() { @Binds @DaggerScope(MainFragment::class) abstract fun bindPresenter(presenter: MainPresenterImpl): MainPresenter @dagger.Module companion object { @Provides @JvmStatic @DaggerScope(MainFragment::class) fun provideService(retrofit: Retrofit) = retrofit.create<NewsService>() } }

  29. fun AppModule()= module { single { androidContext().resources } } fun Module() = module { factory<Presenter> { MainPresenter(service = get(), scheduler = get("main")) } factory<NewsService> { get<Retrofit>().create()} }

  30. @Module abstract class NetworkModule private constructor() { @Module companion object { @JvmStatic @Provides @DaggerScope(BaseApplication::class) fun provideRetrofitClient( @NetworkThread networkScheduler: Scheduler ): Retrofit = Retrofit.Builder() .addCallAdapterFactory( RxJava2CallAdapterFactory.createWithScheduler(networkScheduler) ) .baseUrl(BuildConfig.API_ENDPOINT) .build() } }

  31. fun AppModule() = module { single { androidContext().resources } } fun Module() = module { factory<Presenter> { MainPresenter(service = get(), scheduler = get("main")) } factory<NewsService> { get<Retrofit>().create() } } fun NetworkModule() = module { single { Retrofit = Retrofit.Builder() .addCallAdapterFactory( RxJava2CallAdapterFactory.createWithScheduler(get("network")) ) .baseUrl(BuildConfig.API_ENDPOINT) .build() } }

  32. Singleton scopes based only on lifecycle, no hierarchy Unstable, prone to change API

  33. Questions?

More Related