Dependency Injection With Koin

In this tutorial, you’ll get to know Koin, one of the most popular new frameworks for dependency injection. By Pablo L. Sordo Martinez.

Leave a rating/review
Download materials
Save for later
Share

Dependency Injection (DI) is one of those “new” concepts that keeps showing up in every blog post on the Internet. In fact, the idea is not new, but a notion that gets revisited from time to time. Mastering DI will allow you to handle large and complex applications in a more convenient way.

In this tutorial, you’ll get to know Koin, one of the most popular new frameworks for DI. You’ll start by learning the basics of DI and how your Android projects can take advantage of it. You’ll then apply it to a sample app that adopts Koin as a DI framework and illustrates its benefits.

DI: A “New” Old Friend

Believe it or not, many Android developers have been using DI since their very first applications. However, most new developers haven’t used any DI framework, such as Dagger 2, on their first projects. This sounds ridiculous, but it’s true. How? Follows a recap of the basics of DI.

In the illustration above, ClassA uses an object instantiated, or created, by itself, whereas ClassB just employs an object instance, regardless of where it comes from. So, it can be stated that DI means that a specific entity or class instance can obtain any dependency it needs from the outer world. In other words, the class  is not concerned how it gets a dependency — just how to use it.

That’s great, but how do you give a dependency to an object? There are two main options:

  • Pass dependencies through the object constructor.
  • Use a DI framework.

Using one or the other depends on the developer, who should based his decision according to the size and complexity of the application. For more information about DI, check out this helpful resource.

To DI or Not to DI?

So what are the pros and cons of using DI in your project? To help you with the answer, take a look at the SOLID principles of object-oriented programming, which are five principles that improve the reusability of code and reduce need to refactor any class. DI is directly related with 2 of these pillars, specifically Single Responsibility Principle and Dependency Inversion. Briefly explained

  • Single Responsibility Principle states that every class or module in a program is responsible for just a single piece of that program’s functionality.
  • Dependency Inversion states that high level modules should not depend on low level modules; both should depend on abstractions.

DI supports these goals by decoupling the creation and the usage of an object. Thus, it allows you to replace dependencies without changing the class that uses them and also reduces the risk of modifying a class because one of its dependencies changed.

This makes DI a great option when an application is expected to grow considerably in size and/or complexity.

Using Koin to Simplify DI

So, why use Koin rather than one of the other DI frameworks? The answer: Koin is more concise and straightforward than the others.

Take the popular Dagger 2 as an example. To use Dagger 2, you first need to become familiar with concepts like module and component and annotations such as @Inject. While the steep learning curve for this framework eventually pays off, to get the most out of it, you still have to learn some advanced concepts such as scope and subcomponents.

In contrast, Koin allows you to simply declare modules, which include potential dependencies, to be used in the project and directly inject them in the class of interest.

Koin Basics

According to the official documentation, you can start using Koin in three simple stages:

  1. Declare a module: Defines those entities which will be injected at some point in the app.
    val applicationModule = module {
      single { AppRepository }
    }
  2. Start Koin: A single line, startKoin(this, listOf(applicationModule)), allows you to launch the DI process and indicate which modules will be available when needed, in this case, only applicationModule.
    class BaseApplication : Application() {
      override fun onCreate() {
        super.onCreate()
        startKoin(this, listOf(applicationModule))
      }
    }
  3. Perform an injection:
    In consonance with Kotlin features, Koin allows to perform lazy injections in a very convenient way.
    class FeatureActivity : AppCompatActivity() {
      private val appRepository: AppRepository by inject()
      ...
    }
val applicationModule = module {
  single { AppRepository }
}
class BaseApplication : Application() {
  override fun onCreate() {
    super.onCreate()
    startKoin(this, listOf(applicationModule))
  }
}
class FeatureActivity : AppCompatActivity() {
  private val appRepository: AppRepository by inject()
  ...
}

One limitation when using Koin is that you can only inject dependencies in Activity classes out of the box. In order to inject dependencies in other class types, you must do it through the corresponding constructors. To solve this problem, Koin allows classes to conform to the KoinComponent interface, so that injections are possible on non-Activity classes. You will see an example of this later on.

Getting Started

Now that the theory is clear, time to get to the action! From this section on, you’ll create an application named Mark me!. You’ll see how an application can include DI while following a proper architecture that advocates for the separation of concerns. You’ll use the popular model-view-presenter (MVP) as the architectural pattern in the presentation layer.

Begin by downloading the starter project using the Download Materials button at the top or bottom of the tutorial. The starter project contains the basic skeleton app and some assets.

The code is organized by functional modules, specifically splash, main and feature. Moreover, the application includes auxiliary packages such as di, for DI, repository, including database feature, model and utils.

Build and run the starter project.

As you can see, the application looks finished. However, before it can function, you still need to add all of the logic. If you tap on either the Attendance or Grading buttons, the application will report a crash due to the lack of implementation of certain functionalities.

Remember that, by default, Android Studio includes the following line in methods overridden from interfaces:

TODO("not implemented") //To change body of created functions use File | Settings | File Templates.

This will cause a crash until this snippet gets replaced with a proper method implementation.