Many experienced Java Developers out there are familiar with Dagger, some have used libraries like Guice, Weld or Context Dependency Injection (CDI) available in the Spring framework for injecting dependencies in their projects. With the adoption of Kotlin you may be wondering which tool/library to use for dependency injection in your next project. Whereas you can use Dagger in a Kotlin project, thanks to the 100% Kotlin interoperability with Java, there are a couple of Kotlin libraries designed specifically for this. In this post we will explore how dependecy injection works using an Android app as an example. We shall use a Kotlin based dependency injection library known as Koin; another that is worth mentioning is KODEIN. I chose Koin because it is simple and has some similarities to Dagger. We shall use GitHub API to build a simple client app to demonstrate how dependency injection works. Let's dive in.

First things first - Koin is a lightweight dependency injection framework written purely in Kotlin that uses neither code generation, nor proxies, nor reflections. It’s inspired by Dagger, and thus transitioning from Dagger is not much of a pain since the concepts are related. But before we dive into dependency injection using Koin, what is dependency injection and why is it useful?

Dependency injection is a programming technique that makes a class independent of its dependencies by decoupling the usage of an object from its creation. It’s the fifth principle of the famous object oriented programming S.O.L.I.D principles designed by Uncle Bob. As per the S.O.L.I.D principles, a class should concentrate on fulfilling its own responsibilities and should not be concerned with creating objects to fulfill those responsibilities.

Consider class A that depends on class B. We can naively create an instance of B in the constructor of class A such that the instance of class B is always supplied when we create an object of class A. (Easy isn’t it, but hold on). Suppose the configuration of class B changes? Then we would need to adapt all the instances of class B to use the new configuration in every place that class B has been used in the project. To add more complexity, what if B depends on other classes? You can imagine the hassle involved, it’s a sure path to spaghetti code. To overcome this we use dependency injection.

Benefits of dependency injection

Your team can benefit from dependency injection (DI) in the following ways:

  • DI makes you write testable code
  • DI reduces boilerplate code because the initialization is handled separately by the injector component
  • DI causes you to write code that is loosely coupled but strongly cohesed
  • Extending/Modifying applications that use DI is easy

(NOTE: DI should only be used where necessary; overusing yeilds less benefits)

Ways of injecting dependencies

There are 3 common ways to inject dependencies:

  • Constructor injection - the dependencies are provided through a class constructor (most common).
  • Setter injection - the client exposes a setter method that the injector uses to inject the dependency.
  • Interface injection - the dependency provides an injector method that will inject the dependency into any client passed to it. Clients must implement an interface that exposes a setter method that accepts the dependency.

Setting up Android Project and Retrieving GitHub Users

The first step is to Create an Android project with a single Activity. Next, add Koin and Retrofit dependencies to the app's build.gradle file. We will use Retrofit to consume GitHub API. We will receive the data as a Completable and subscribe to it.


// Add Jcenter to your repositories if needed
repositories {
    jcenter()
}

dependencies {
    //...other Android dependencies included here
    // add koin and retrofit dependencies
    implementation 'org.koin:koin-android:2.1.6'
    implementation 'com.squareup.retrofit2:retrofit:2.9.0'
    implementation 'com.squareup.retrofit2:adapter-rxjava2:2.9.0'
    implementation 'com.squareup.retrofit2:converter-moshi:2.9.0'
    implementation 'io.reactivex.rxjava2:rxkotlin:2.3.0'
    implementation 'io.reactivex.rxjava2:rxandroid:2.1.0'   
}

Create API service

Retrofit requires that you declared HTTP APIs as interfaces.

interface GitHubService {
  @GET("users")
  fun fetchUsers(@Query("since") since: Int): Completable<List<GithubUser>>
}

Retrofit builder

Next we instantiate Retrofit class through its builder. The class will generate implementation of the GitHubService class.

val retrofit = Retrofit.Builder()
    .baseUrl("https://api.github.com/")
    .addConverterFactory(MoshiConverterFactory.create())
    .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
    .build();

We will use the retrofit instance in the next section.

Koin - Dependency Injection Framework

Koin components

Let’s create a UsersRepository to provide some data. We will subscribe to the Completable returned by fetchUsers method of the GitHubService class.

interface UserRepository {
    fun getUsers(): List<GithubUser>
}

class GitHubUserRepository : UserRepository {

    val gitHubService = retrofit.create(GitHubService.class);
    
    override fun getUsers(): List<GithubUser>{
        gitHubService.fetchUsers(2000)
            .observeOn(AndroidSchedulers.mainThread())
            .subscribeOn(Schedulers.io())
            .subscribeBy(
                onError = { Log.e("GitHubUserRepository",  "Error $it")},
                onSuccess = { return $it }                 
            )
    }
}

GithubUser is the model class with only two properties, name and username, created using the Kotlin data class.

 data class GithubUser(var name: String?, val username: String)

Let’s create a presenter class to consume UsersRepository class we implemented earlier.

class GithubUserPresenter(val repo: UserRepository) {
    fun getGithubUsers(): List<GithubUser> = repo.getUsers()
}

Declaring Koin Modules

Modules are declared in Koin using module function.

val appModule = module {
    // Single instance of UserRepository - Singleton
    single<UserRepository> { GitHubUserRepository() }
    // Simple Presenter Factory - new instances created each time
    factory { GithubUserPresenter(get()) }
}

NOTE: When a class is declared as factory a new instance will be created each time it is needed. Classes declared as single are instantiated once and the same instance is used throughout the lifetime of the application.

Starting Koin

Once the modules are declared we can start Koin at the application level. To do this we will extend the Android Application class then declare it on the manifest.xml file.

class GitApplication : Application(){
    override fun onCreate() {
        super.onCreate()
        // Start Koin
        startKoin {
            androidLogger()
            androidContext(this@GitApplication)
            modules(appModule)
        }
    }
}

Injecting dependencies

Now that we have started Koin, let’s see how to inject a dependency in our MainActivity class. There are two ways to retrieve instances of our components (using inject or get functions). The inject function allows us to retrieve Koin instances at runtime (lazily) whereas get retrieves the instance directly (eagerly).

class MainActivity : AppCompatActivity() {
    // Lazy injected GithubUserPresenter
    val githubPresenter: GithubUserPresenter by inject()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        Log.d("GithubUsers", githubPresenter.getGithubUsers())
    }
}

Here we are simply retrieving the list of github users and logging them to the Android console.

When the GithubUserPresenter object is created it is also supplied the UserRepository class that it depends on.

Conclusion

We have come to the end of the post and have seen how easy it is to inject dependencies using Koin DI framework. A quick recap when working with Koin: begin by creating components, next expose your components through a Koin module, start koin, and finally inject you dependencies as desired. If you want to take this further, explore Koin's full documentation on their website.

Happy coding!

You've successfully subscribed to Decoded For Devs
Welcome back! You've successfully signed in.
Great! You've successfully signed up.
Your link has expired
Success! Your account is fully activated, you now have access to all content.