Koin vs Dagger, Say hello to Koin

After many times trying to learn Dagger finally I found Koin. Koin saves my time, my life and my brain.

This article explains what Koin is, compare with Dagger and how to use it.

What is Koin

Koin is a simple powerful Dependency injection framework for Kotlin. Written in pure Kotlin using functional resolution only: no proxy, no code generation, no reflection!

Compare to Dagger

In order to properly compare the two implementation options, we will focus on a project that Implemented first with Dagger and now with Koin. The architecture of this project is MVVM and it uses retrofit and LiveData. My project has an Activity, four fragments, five view models, a repository and a web service interface, so we can say it’s a small project just for designing a base for my future applications.

First look the DI package before and after Koin

As you see, Dagger configuration needs pretty much more files to write. for example three files to injecting view models!! ( Really ? is it necessary? )

Lines of code:

To get these numbers I used Statistic. I used it twice before and after compiling the project with both Dagger and Koin. The result was amazing.

Lines of code before and after compile

As you can see Dagger generated line of code two times more than Koin!

How about Build time:

I tried to clean and rebuild both project. I got this result:

Koin:
BUILD SUCCESSFUL in 17s
88 actionable tasks: 83 executed, 5 up-to-date
Dagger:
BUILD SUCCESSFUL in 18s
88 actionable tasks: 83 executed, 5 up-to-date

I think this result shows us that in a bigger and real project this time could be expensive.

Setup

Another thing is Setting up these libraries. think you want to use Dagger with MVVM and Android Support lib so you have to do this:

First adding Dagger SDK to module gradle:

kapt "com.google.dagger:dagger-compiler:$dagger_version"
kapt "com.google.dagger:dagger-android-processor:$dagger_version"
implementation "com.google.dagger:dagger:$dagger_version"

After creating modules and components you have to init Dagger in Application class (or other ways to initializing Dagger)

Class MyApplication : Application(), HasActivityInjector { 
@Inject
lateinit var dispatchingAndroidInjector: DispatchingAndroidInjector<Activity>
override fun activityInjector() = dispatchingAndroidInjector
fun initDagger() {
DaggerAppComponent
.builder()
.application(this)
.build()
.inject(this)
}
}

Like this, in all activities or in our BaseActivity, we need to implement HasSupportFragmentInjector and inject DispatchingAndroidInjector.

For view models, we need to inject ViewModelFactory in BaseFragment and also implement Injectable.

But it not about all. There are more to do.

For each ViewModel, Fragment, and Activity we need to tell DI to how to inject them. As you can see we have ActivityModule, FragmentModule, and ViewModelModule.

Let me show you some codes:

@Module
abstract class ActivityModule {
@ContributesAndroidInjector(modules = [FragmentModule::class])
abstract fun contributeMainActivity(): MainActivity

//Add your other activities here
}

For Fragments:

@Module
abstract class FragmentModule {
@ContributesAndroidInjector
abstract fun contributeLoginFragment(): LoginFragment

@ContributesAndroidInjector
abstract fun contributeRegisterFragment(): RegisterFragment

@ContributesAndroidInjector
abstract fun contributeStartPageFragment(): StartPageFragment
}

And for ViewModels:

@Module
abstract class ViewModelModule {

@Binds
abstract fun bindViewModelFactory(factory: ViewModelFactory): ViewModelProvider.Factory

@Binds
@IntoMap
@ViewModelKey(loginViewModel::class)
abstract fun bindLoginFragmentViewModel(loginViewModel: loginViewModel): ViewModel

@Binds
@IntoMap
@ViewModelKey(StartPageViewModel::class)
abstract fun bindStartPageViewModel(startPageViewModel: StartPageViewModel): ViewModel
    ......
}

So you have to care about adding new Fragments, Activities, and ViewModels to DI modules, also, you have to say all developers to care about that too.

What about Koin:

Just add this line to your gradle:

implementation "org.koin:koin-android-viewmodel:$koin_version"

And create your module file, I’ll talk about it later but I could say we don’t need any of these classes.

There is some other problem with Dagger.

Learning Dagger is hard, so if someone joins your project or team he/she has spent a lot of time on learning Dagger. I used to Dagger since two years ago and now I don’t know everything about that, every time I started to use new tech in android I have to search and learn about Dagger to implement this new tech with Dagger.


Time To Code

Ok, after all, let’s coding.

As you know we have to add Koin Dependency first, so let’s do it:

implementation "org.koin:koin-android-viewmodel:$koin_version"

This version of Koin is used for Koin with ViewModel, there are some other dependencies to use Koin, But we want to use it with MVVM

After adding Koin dependency we can implement our first module, just like Dagger we can implement every module in a separate file, but because of code simplicity, I decided to implement all modules in a file. you can separate it later.

First, we have to know some useful notes about koin syntax:

  • get() to resolve an instance in a Koin module, just use the get() function to the requested needed component instance. this get() function is usually used into the constructor, to inject constructor values.
  • factory factory component declaration is a definition that will gives you a new instance each time you ask for this definition
  • single provide a singleton bean definition (also aliased as bean)
  • name = is used to name definitions. This is required when you want to have multiple instances of the same class with different types.

That's it. Instead of creating many files with multiple annotations, multiple components, we create a simple and readable for every one class for DI!!

Explain the code

private val retrofit: Retrofit = createNetworkClient()

createNetworkClient is a function to create a Retrofit instance. setting baseUrl, adding ConverterFactory and Interceptor done there.

private val generalApi: GeneralApi =  retrofit.create(GeneralApi::class.java)
private val authApi: AuthApi = retrofit.create(AuthApi::class.java)

AuthApi and GeneralApi are retrofit endpoints interfaces. we just add our endpoint here to separate our endpoint.

val viewModelModule = module {
viewModel { LoginFragmentViewModel(get()) }
viewModel { StartPageViewModel() }
}

we declare our viewModels class as a viewModel in a module. Koin will give a viewModel to the lifecycle ViewModelFactory and help bind it to the current component.
as you can see we have a get() method into the LoginFragmentViewModel constructor. this function resolves an instance for LoginFragmentViewModel. here it’s AuthRepo.

After all, we have to say DI to generate codes, but How?
It’s a piece of cake

in your Application class in the onCreate Method write this:

startKoin(this, listOf(repositoryModule, networkModule, viewModelModule))

Here we simply call the startKoin method, passing in a context and a list of the modules which we wish to initialize Koin with.

Using ViewModel is easier than pure ViewModel now!
in your View class (Fragment, Activity) add this:

private val startPageViewModel: StartPageViewModel by viewModel()

By this code, koin creates an object of StartPageViewModel for you and fill the startPageViewModel with it. Now you can use your view model in your view.


Hope that it makes you want to try Koin