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

Dependency Injektion with Koin and Kodein

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.

Zachary Smith

September 19, 2018
Tweet

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/