Android Dependency Injection with Koin

levimoreira

Levi Albuquerque

Posted on February 25, 2019

Android Dependency Injection with Koin

Dependency Injection, or DI, is a design pattern/technique as old as time itself. You've probably heard it a couple of times during your career and it always comes up in the list of "good practices" when developing Android apps, but why is that?
Let's first understand a bit of what DI is and how it was done in Android and then we can have a look at how Koin approaches the idea.

What is DI?

Dependency Injection consists in simply providing the dependencies needed by an object without creating them within the object itself. As the name says we'll inject that dependency somehow into the object. I know, it sounded a bit confusing, let's see an example:

Let's say you have a repository class in your app that you can use to access your api service. You could create your class as such:

class MovieRepositoryImpl() :
    MovieRepository {
    val tmdbService = TmdbApi()

    override fun getMovies(page: Int): Single<TopMovieListResponse> {
        return tmdbService.getTopMovies(page = page)
    }
}

Enter fullscreen mode Exit fullscreen mode

This however imposes a strong coupling between the service class and the repository, you're creating the service within the repository. How would you go about unit testing the Repository? You can't test just the repository because the service is there and you can't control how it behaves. Dependency Injection favors and helps us to create a mindset to enable writing testable code. We could modify the code and provide the service dependency when we create the repository, through the constructor:

class MovieRepositoryImpl(val tmdbService : TmdbApi) :
    MovieRepository {

    override fun getMovies(page: Int): Single<TopMovieListResponse> {
        return tmdbService.getTopMovies(page = page)
    }
}
Enter fullscreen mode Exit fullscreen mode

Now when we try to test the repository we can provide a mock instance of the service and we can correctly unit test the repository. This is still not DI per se, but rather an approach to coding that enables us to do it later. We can write code that makes DI easier (and makes testing easier) providing dependencies in various manners:

  • Constructor Injection: This is the preferable way of organizing your code, we should try to always provide dependencies at the moment of creation of an object. This however can become a bit messy if there are a large number of dependencies.

  • Field Injection: This type of injection requires some type of harness that will populate the field with the desired dependency. This makes testing a bit harder, but if we make use of a DI framework (more soon) we can choose how this field is populated and we can provide mocks to be injected in them.

  • Method Injection: We can make use of a setter method to provide the right dependency. By using a setter method, however, we can end-up with an object that is in an invalid state.

What is a DI Framework

A DI framework is nothing more than a tool to help you organize how DI is done in your code. Depending on the framework you'll need to follow some steps in order to create a new class. By using a DI framework we can leverage from both constructor and field injections in a more organized way that enables us to develop a more decoupled and testable code. A very famous DI framework used in Android is Dagger, more specifically Dagger2 the latest version of it. About Dagger2 I have one thing to say:

steep

The learning curve for Dagger2 is pretty steep and you can find many series online explaining how to setup Dagger2 in your project. The first configuration is always the toughest, after that Dagger is easily extendable and adding new dependencies is as easy as adding arguments to a method. For the purposes of learning how to apply Koin you'd need some idea of how Dagger works, specially the idea of Modules and Dependency graph.

  • Module: A module is a central place where you can tell the framework how to create new instances of a dependency. You can add a variety of configurations in this module to help the framework decide how the new instance will be created. This is how a module looks like in Dagger2 for instance:
@Module
abstract class MainProfileModule {

    @Binds
    abstract fun provideView(
       view: MainProfileFragment
    ): MainProfileContract.View

    @Binds
    abstract fun providePresenter(
       presenter: MainProfilePresenter
    ): MainProfileContract.Presenter

    @Binds
    abstract fun provideDataHandler(
       dhandler: MainProfileDataHandler
    ): MainProfileContract.DataHandler
}
Enter fullscreen mode Exit fullscreen mode
  • Dependency graph: It's an "imaginary" (in memory graph) that the framework uses to keep track of where the dependencies are injected.

Koin

This is Koin as stated by the creators:

A pragmatic lightweight dependency injection framework for Kotlin developers. Written in pure Kotlin using functional resolution only: no proxy, no code generation, no reflection!

Here's some differences between Koin and Dagger:

  • Dagger2 is written in Java, Koin is purely kotlin so it looks more natural when used with Kotlin.
  • Koin works as a Service Locater masked as Dependency Injection (if that makes any sense)
  • Koin does not generate code == smaller build time
  • Koin requires more manual configuration as opposed to using Annotations like Dagger2.

This all means a nice kotlinesque alternative to Dagger2. This doesn't mean we should start replacing Dagger with Koin in every project we start from now on. Dagger is very powerful and once you get past the initial setup, it's quite easy to use. Koin is just another option to managing dependencies in a nice organized way, if you'll use it or not it will depend on your needs.

To add Koin into your project just add the dependencies to your app level build.gradle file:

    implementation "org.koin:koin-android:${koin}"
    implementation "org.koin:koin-android-viewmodel:${koin}"
Enter fullscreen mode Exit fullscreen mode

Usage and Configuration

The project I'll be using to demonstrate the use of Koin can be found here. It's a simple app that shows the top rated movies from the TMDB API. Here's a peek of how it looks like:

app image

After I've setup most of the classes in my code (keeping in mind the DI mindset) I've started writing my modules. I've started with the network module where I keep all network related injections.

Some observations from the gist:

  • means we need this instance to be a singleton. If you'd like to create a new instance every time the dependency is needed you can use "factory" instead.
  • When defining how/what type of instance you'd like koin to create you can specify a name and therefore have a named instance. This exists in Dagger through the @Named annotation and is specially useful if you have many instances of the same type.
  • If you'd like Koin to provide an instance for you when creating another instance you can use the get() method and pass an optional name if it is an named instance. Koin whill locate the instance and inject it when creating this new object.

I've also created a separate module to concentrate all "Movie" related object creation:

In this gist you can see one of the nicest things about Koin. It has support for Arch Components, specially ViewModel. When I tried to use Dagger2 and ViewModel I had to inject a ViewModelFactory and from that create my ViewModel, in here we need only to declare it as a and it will be easily injectable in our activity:

val movieViewModel: MovieViewModel by viewModel()
Enter fullscreen mode Exit fullscreen mode

If your object is not a view model it can easily be injected as well, just use either inject() or get():

// Property Injection
val myRepository: MovieRepository by inject()

// Direct instance request
val myRepository: MovieRepository = get()
Enter fullscreen mode Exit fullscreen mode

The last piece of configuration that we need to do is to initialize Koin. This needs to be done in the onCreate method from your Application class:

startKoin(this, listOf(appModule, networkModule, movieModule))
Enter fullscreen mode Exit fullscreen mode

And simple as that all pieces are connected and you've created an easily testable app with decoupled dependencies :)

💖 💪 🙅 🚩
levimoreira
Levi Albuquerque

Posted on February 25, 2019

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related