Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Dependency Injektion with Koin and Kodein

Sponsored · Ship Features Fearlessly Turn features on and off without deploys. Used by thousands of Ruby developers.
Avatar for Zachary Smith Zachary Smith
September 19, 2018

Dependency Injektion with Koin and Kodein

In this talk, I give an overview of what dependency injection is and how Dagger, the current go-to DI framework on Android, provides this for us. Then I discuss some of Dagger's short comings and how we can use Koin and Kodein as alternatives, albeit with some important trade-offs to consider.

Avatar for Zachary Smith

Zachary Smith

September 19, 2018

More Decks by Zachary Smith

Other Decks in Technology

Transcript

  1. What is Dependency Injection? • Dependency injection is a technique

    for applying the principle of Inversion of Control to how we manage our dependencies • The goal of implementing dependency injection is to achieve separation of concerns in our code base • We want to separate the creation of objects from their use • Dagger has been the de facto way of doing this in android for a while • Koin and Kodein are Kotlin libraries that offer an alternative • These libraries accomplish it in a different way than Dagger
  2. Service Locator vs Dependency Injection • Both solve the problem

    of decoupling our code • Application code is independent of instantiating concrete implementations
  3. Service Locator vs Dependency Injection It is important to note

    the difference in their implementation: • With the service locator, our application code asks the locator for specific dependencies • With dependency injection, there is no direct request for dependencies. • Dependencies are injected by the framework • With a service locator, every class needing injected dependencies has a dependency on the service locator.
  4. class MovieBrowserFragment : Fragment() { private lateinit var viewModel: MovieBrowserViewModel

    override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) viewModel = ViewModelProviders.of(this).get(MovieBrowserViewModel::class.java) } ... }
  5. class MovieBrowserViewModel : ViewModel() { private val gson = GsonBuilder()

    .setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES) .create() private val apiKeyInterceptor = ApiKeyInterceptor() private val okHttpClient = OkHttpClient.Builder() .addInterceptor(apiKeyInterceptor) .build() private val retroFit = Retrofit.Builder() .client(okHttpClient) .baseUrl(BASE_URL) .addConverterFactory(GsonConverterFactory.create(gson)) .addCallAdapterFactory(RxJava2CallAdapterFactory.createAsync()) .build() private val movieService: MovieService = retroFit.create(MovieService::class.java) private val movieRepository: MovieRepository = MovieRepositoryImpl(movieService) ... }
  6. Dagger • Dagger is a fully static, compile-time dependency injection

    framework for both Java and Android. • Dagger uses annotation processing and code generation to build a graph of your application’s dependencies at compile time. • “If it compiles, it will work…”
  7. class MovieBrowserViewModel : ViewModel() { private val gson = GsonBuilder()

    .setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES) .create() private val apiKeyInterceptor = ApiKeyInterceptor() private val okHttpClient = OkHttpClient.Builder() .addInterceptor(apiKeyInterceptor) .build() private val retroFit = Retrofit.Builder() .client(okHttpClient) .baseUrl(BASE_URL) .addConverterFactory(GsonConverterFactory.create(gson)) .addCallAdapterFactory(RxJava2CallAdapterFactory.createAsync()) .build() private val movieService: MovieService = retroFit.create(MovieService::class.java) private val movieRepository: MovieRepository = MovieRepositoryImpl(movieService) ... }
  8. @Singleton @Component(modules = [ AppModule::class, ViewModelModule::class, ServiceModule::class, RepositoryModule::class ]) interface

    Injector { fun inject(browserFragment: MovieBrowserFragment) fun inject(detailsFragment: MovieDetailsFragment) }
  9. @Module class AppModule() { @Provides @Singleton fun provideApplication(): MovieFinderApplication {

    ... } @Provides @Singleton fun provideGson(): Gson { ... } @Provides @Singleton fun provideRetroFit(gson: Gson, okHttpClient: OkHttpClient): Retrofit { ... } @Provides @Singleton fun provideApiKeyInterceptor(): ApiKeyInterceptor { ... } @Provides @Singleton fun provideOkHttpClient(apiKeyInterceptor: ApiKeyInterceptor): OkHttpClient { ... } }
  10. @Module abstract class ViewModelModule { @Binds internal abstract fun bindViewModelFactory(factory:

    ViewModelFactory): ViewModelProvider.Factory @Binds @IntoMap @ViewModelKey(MovieBrowserViewModel::class) internal abstract fun movieResultsViewModel(viewModel: MovieBrowserViewModel): ViewModel @Binds @IntoMap @ViewModelKey(MovieDetailsViewModel::class) internal abstract fun movieDetailsViewModel(viewModel: MovieDetailsViewModel): ViewModel }
  11. class MovieFinderApplication : Application() { lateinit var injector: Injector private

    set override fun onCreate() { super.onCreate() injector = DaggerInjector.create() } }
  12. class MovieBrowserFragment : Fragment() { private lateinit var viewModel: MovieBrowserViewModel

    @Inject lateinit var viewModelFactory: ViewModelFactory override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) val injector = (this.activity!!.application as MovieFinderApplication).injector injector.inject(this) viewModel = ViewModelProviders.of(this, viewModelFactory) .get(MovieBrowserViewModel::class.java) }
  13. class MovieBrowserFragment : Fragment() { private lateinit var viewModel: MovieBrowserViewModel

    @Inject lateinit var viewModelFactory: ViewModelFactory override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) getInjector().inject(this) viewModel = getViewModel(viewModelFactory) } }
  14. Koin • A lightweight dependency injection framework for Kotlin •

    Written in Kotlin, uses functional resolution only • Leverages Kotlin’s lazy delegate, inline functions, and reified generic types to resolve dependencies • Provides Android specific integrations ◦ Simplifies injection of Architecture Components ViewModels with constructor parameters • Simple, declarative DSL with idiomatic Kotlin API
  15. val appModule = module { single { provideGson() } single

    { provideApiKeyInterceptor() } single { provideOkHttpClient(apiKeyInterceptor = get()) } single { provideRetroFitInstance(gson = get(), okHttpClient = get()) } } private fun provideRetroFitInstance(gson: Gson, okHttpClient: OkHttpClient): Retrofit { … } private fun provideApiKeyInterceptor(): ApiKeyInterceptor { … } private fun provideOkHttpClient(apiKeyInterceptor: ApiKeyInterceptor): OkHttpClient { … } private fun provideGson(): Gson { … }
  16. /** * Resolve a component * @param name : component

    canonicalName * @param parameters - injection parameters */ inline fun <reified T : Any> get( name: String? = null, noinline parameters: ParameterDefinition = emptyParameterDefinition() ): T = if (name != null) koinContext.get( name, parameters = parameters ) else koinContext.get(parameters = parameters)
  17. val serviceModule = module { single { createService<MovieService>(retrofit = get())

    } } private inline fun <reified T> createService(retrofit: Retrofit): T { return retrofit.create(T::class.java) } val repositoryModule: Module = module { single { MovieRepositoryImpl(movieService = get()) as MovieRepository } } val viewModelModule = module { viewModel { MovieBrowserViewModel(movieRepository = get()) } viewModel { MovieDetailsViewModel(getMovieDetailsInteractor = get()) } }
  18. class MovieFinderApplication : Application() { override fun onCreate() { super.onCreate()

    startKoin(this, koinModules) } private val koinModules = listOf( appModule, serviceModule, repositoryModule, interactorModule, viewModelModule ) }
  19. /** * Lazy getByClass a viewModel instance * * @param

    key - ViewModel Factory key (if have several instances from same ViewModel) * @param name - Koin BeanDefinition name (if have several ViewModel beanDefinition of the same type) * @param parameters - parameters to pass to the BeanDefinition */ inline fun <reified T : ViewModel> LifecycleOwner.viewModel( key: String? = null, name: String? = null, noinline parameters: ParameterDefinition = emptyParameterDefinition() ) = viewModelByClass(T::class, key, name, null, parameters)
  20. /** * Lazy getByClass a viewModel instance * * @param

    clazz - Class of the BeanDefinition to retrieve * @param key - ViewModel Factory key (if have several instances from same ViewModel) * @param name - Koin BeanDefinition name (if have several ViewModel beanDefinition of the same type) * @param from - ViewModelStoreOwner that will store the viewModel instance. null to assume "this" as the ViewModelStoreOwner * @param parameters - parameters to pass to the BeanDefinition */ fun <T : ViewModel> LifecycleOwner.viewModelByClass( clazz: KClass<T>, key: String? = null, name: String? = null, from: ViewModelStoreOwnerDefinition? = null, parameters: ParameterDefinition = emptyParameterDefinition() ) = lazy { getViewModelByClass(clazz, key, name, from, parameters) }
  21. Kodein • Kodein is a dependency injection / retrieval container

    • Like Koin, leverages Kotlin’s lazy delegate, inline functions and reified generic types to resolve dependencies • Simple, declarative DSL with idiomatic Kotlin API • Android specific integrations
  22. val appModule = Kodein.Module(name = "AppModule") { bind<Gson>() with singleton

    { provideGson() } bind<ApiKeyInterceptor>() with singleton { provideApiKeyInterceptor() } bind<OkHttpClient>() with singleton { provideOkHttpClient(apiKeyInterceptor = instance()) } bind<Retrofit>() with singleton { provideRetroFitInstance(gson = instance(), okHttpClient = instance()) } }
  23. val serviceModule = Kodein.Module(name = "ServiceModule") { bind<MovieService>() with singleton

    { createService<MovieService>(instance()) } } val repositoryModule = Kodein.Module(name = "RepositoryModule") { bind<MovieRepository>() with singleton { MovieRepositoryImpl(movieService = instance()) as MovieRepository } } val viewModelModule = Kodein.Module(name = "ViewModelModule") { bind<MovieBrowserViewModel>() with singleton { MovieBrowserViewModel(movieRepository = instance()) } bind<MovieBrowserViewModelFactory>() with singleton { MovieBrowserViewModelFactory(movieBrowserViewModel = instance()) } }
  24. class MovieFinderApplication : Application(), KodeinAware { override val kodein: Kodein

    = Kodein.lazy { import(appModule) import(serviceModule) import(repositoryModule) import(viewModelModule) } }
  25. class MovieBrowserFragment : Fragment(), KodeinAware { override val kodein: Kodein

    by closestKodein() private val viewModelFactory: MovieBrowserViewModelFactory by instance() private val movieBrowserViewModel: MovieBrowserViewModel by lazy { ViewModelProviders.of(this, viewModelFactory).get(MovieBrowserViewModel::class.java) } ... }
  26. /** * Binds the previously given type and tag to

    the given binding. * * @param binding The binding to bind. * @throws OverridingException If this bindings overrides an existing binding and is not allowed to. */ infix fun <C, A> with(binding: KodeinBinding<in C, in A, out T>) = containerBuilder.bind( Key(binding.contextType, binding.argType, type, tag), binding, moduleName, Overrides )
  27. /** * Creates a singleton: will create an instance on

    first request and will subsequently always return the same instance. * * T generics will be kept. * * @param T The created type. * @param creator The function that will be called the first time an instance is requested. Guaranteed to be called only once. Should create a new instance. * @return A singleton ready to be bound. */ inline fun <EC, BC, reified T: Any> Kodein.BindBuilder.WithScope<EC, BC, Unit>.singleton( ref: RefMaker? = null, noinline creator: NoArgSimpleBindingKodein<BC>.() -> T ) = Singleton(scope, contextType, generic(), ref, creator)
  28. Dagger vs Koin vs Kodein All three of these frameworks/libraries

    provide resolution of dependencies without using reflection They all provide a mechanism for decoupling the instantiation of dependencies from their use in application code
  29. Dagger • Dagger uses code generation at compile time to

    generate code that satisfies the dependency graph for your application • Dagger is compile time validated ◦ Code is generated at compile time and will fail compilation if dependencies can’t be satisfied • Annotation processing and code generation is slow • Dagger is true dependency injection* ◦ This comes at the cost of a steeper learning curve and more verbose api * It is possible define a component interface which exposes dependencies that you can ask for directly. Using dagger in this way is the same as a service locator.
  30. Dagger • Dagger cannot inject private or immutable fields ◦

    Injected fields must be use lateinit var • Dagger will automatically instantiate objects with @Inject annotated • No incremental annotation processing for dagger • Must regenerate entire dependency graph each build
  31. Koin • Uses lazily evaluated, inline functions and with reified

    generic types to resolve dependencies • Will crash at run-time if it can not resolve requested dependency • Can only be use with Kotlin • Is technically a service locator ◦ Wraps the access of the service locator in lazily executed extension functions ◦ Application code must ask for specific dependencies at run time • Koin is slightly less verbose, and more readable API than Kodein
  32. Kodein • Uses lazily evaluated, inline functions and with reified

    generic types to resolve dependencies • Will crash at run-time if it can not resolve requested dependency • Allows you to retrieve (but not define) dependencies in Java • Supports multi-platform ◦ JVM, Javascript, and Native • Is technically a service locator ◦ Wraps the access of the service locator in lazily executed extension functions ◦ Application code must ask for specific dependencies at run time • More verbose and less readable API
  33. Resources • Koin Documentation ◦ https://insert-koin.io/ • Kodein Documentation ◦

    http://kodein.org/Kodein-DI/ • Inversion of Control Containers and the Dependency Injection pattern ◦ https://martinfowler.com/articles/injection.html • A brief look at Koin on Android ◦ https://overflow.buffer.com/2018/09/13/a-brief-look-at-koin-on-android/ • Dependency injection with Dagger 2 – Introduction to DI ◦ https://mirekstanek.online/dependency-injection-with-dagger-2-introduction-to-di/ • That Missing Guide: How to use Dagger2 ◦ https://medium.com/@Zhuinden/that-missing-guide-how-to-use-dagger2-ef116fbea97 • Dependency Injection and Service Locators in a Kotlin world with Danny Preussler ◦ http://fragmentedpodcast.com/episodes/133/