Dependency Injection using Koin

Mobile Development Sep 16, 2022

This article demystifies benefits of using Dependency Injection in our android apps and how it helped Halodoc application. So let's begin with...

What is Dependency Injection?

Main idea of Dependency Injection is to resolve all dependencies centrally. We can have separate code block for instantiation of classes. When using Dependency Injection, objects are given their dependencies at run time as well as compile time.

How it benefited Halodoc?

Halodoc is a multi modular project where all modules collaborate with each other to perform some useful stuff. Of all multiple available Dependency Injection frameworks, Halodoc uses Koin. Let us see what's the reason for that.

Why Halodoc uses Koin?

In Koin, there's a service locator, that means client code requests the dependencies.

Koin uses scope to manage android component lifecycles.

Halodoc application uses Koin having ability to replace dependencies without changing the class that uses it.

Koin is a lightweight framework, developer friendly, super easy to learn and having very less boilerplate code.

Dependencies can be injected into objects by many means, here are those:

  1. Using constructor injection:

This is very simple and easy type of injection. Here's the sample code to inject using Constructor injection.

    single { AppConfigSource(get(), get()) }
    
    class AppConfigSource(private val param1, private val param2) {
           // your code                 
    }

2. Using by inject { }:

Similar to lazy delegate expression by lazy { }, Koin provides us by inject { }. Here's the sample code.

    private val appConfigSource: AppConfigSource by inject {
        parametersOf(param1, param2)
    }

 3. Using setter injection:

    class AppConfigSource(private val param1, private val param2) {
           lateinit var classA: ClassA
           lateinit var classB: ClassB
    }
    
    val appConfigSource: AppConfigSource = AppConfigSource(param1, param2)
    
    // first way injecting property separately
    appConfigSource::classA.inject()
    appConfigSource::classB.inject()

    // second way injecting all properties at ones
    appConfigSource.inject(appConfigSource::classA, appConfigSource::classB)

As Koin resolves dependencies at runtime, it slightly affects the runtime performance compared to Dagger and Hilt. But we at Halodoc continue to use Koin looking for cross platform compatibility i.e., Kotlin Multiplatform Mobile, even though there are more better compile time dependency injection frameworks available in market. To learn more about KMM, you can refer here.

Dependency injection made testing easier in Halodoc application. Test class extends KoinTest to inject and get anything from Koin graph. KoinTestRule can also be used to create test modules that provide mocks.

How Halodoc implements Koin?

Step 1: We added both koin and koin-test in the module project.

    "org.koin:koin-android:2.1.5"
    "org.koin:koin-test:2.1.5"

Note: We need to implement KoinComponent in the classes we inject dependency as well as use it. It helps inject into a Worker.

Step 2: We want to inject a module, Koin gives us facility to declare or modify the behaviour of that class by applying Singleton or Factory pattern.

Singleton pattern: Using single { } keyword, define a singleton that creates only a single instance for type ProductConfig. This returns our module with return type module, and that we store it in prodConfigModule variable.

    val prodConfigModule = module {

        single { (appConfig: JSONObject, buildConfig: JSONObject,
                 appConfigApi: String, abExpConfig : JSONObject) ->
            ProductConfig(
                appConfig,
                buildConfig,
                appConfigApi,
                abExpConfig
            )
        }
    }

Factory pattern: Using  factory { } keyword, define a factory that creates a new instance each and every time for type ProductConfig.

    val prodConfigModule = module {

        factory { (appConfig: JSONObject, buildConfig: JSONObject,
                 appConfigApi: String, abExpConfig : JSONObject) ->
            ProductConfig(
                appConfig,
                buildConfig,
                appConfigApi,
                abExpConfig
            )
        }
    }

Step 3: Now as we have declared our single definition of ProductConfig module, we will go to our application class and start Koin and load our desired modules. Below code block initialises Koin. The keyword startKoin is a main entry point to launch Koin container. And the other are extension function where androidLogger is a Android Logger for Koin, androidContext adds context instance to Koin container, and androidFileProperties loads properties file from assets.

    startKoin {
        // use AndroidLogger as Koin Logger - default Level.INFO
        androidLogger(Level.ERROR)

        // use the Android context given there
        androidContext(application)

        // load properties from assets/koin.properties file
        androidFileProperties()
    }

Step 4: We now load our desired modules using below code block. The keyword loadKoinModules() loads Koin module in global Koin context.

    loadKoinModules(prodConfigModule)

That's all is required to inject a module dependency. Now we will shift to our main app Halodoc project, where we will use this injected module dependency to get the product config stuff. We'll repeat step 1 and step 3 for main project.

Step 5: We want to use our injected module so firstly we will get the injected value using get().

    val productConfig: ProductConfig = get()

That's it, we have successfully injected a module dependency into our project. ProductConfig module is now accessible in our main project using productConfig variable.

Below are the main advantages that stand out in Halodoc application after using Dependency Injection.

Advantages of using Dependency Injection:

1. Readability:

Code that uses DI is more straightforward. Constructors aren’t as cluttered and filled with logic. Mostly we use setter methods to inject dependencies.

2. Maintainability:

More the readable code is, more maintainability the code has. This can be considered as rewarding for developers. As we have classes that are loosely coupled, the code is easier to maintain.

3. Flexibility:

This can be also called as Reusability. Loosely coupled code/classes can easily be reused in different situations. Loosely coupled code that uses dependency injection is flexible, extensible and able to adapt reusability, thus making Flexibility one of Dependency Injection's ability.

4. Testability:

Loosely couple classes are very easy to unit test. More testing means higher quality.

5. Team-Ability:

Another important benefit of DI. Working in a team, together on a project, Dependency Injection will facilitate team development. Dependency Injections prefers code against abstractions and not implementations. If you have two teams working together, each needing the other’s work, abstractions can be defined before doing the implementations. Then each team can write their code using the abstractions, even before the implementations are written.

Thus, Dependency Injection results in readable, flexible, maintainable, and testable code that is easily spread between team members.

Conclusion:

In this article, we have gone through dependency injection, how it helped Halodoc application improve code base and understood detailed advantages it brought to the application using Koin, along with how Halodoc implements Koin in the application.

Join us

Scalability, reliability and maintainability are the three pillars that govern what we build at Halodoc Tech. We are actively looking for engineers at all levels and  if solving hard problems with challenging requirements is your forte, please reach out to us with your resumé at careers.india@halodoc.com.


About Halodoc

Halodoc is the number 1 all around Healthcare application in Indonesia. Our mission is to simplify and bring quality healthcare across Indonesia, from Sabang to Merauke. We connect 20,000+ doctors with patients in need through our Tele-consultation service. We partner with 3500+ pharmacies in 100+ cities to bring medicine to your doorstep. We've also partnered with Indonesia's largest lab provider to provide lab home services, and to top it off we have recently launched a premium appointment service that partners with 500+ hospitals that allow patients to book a doctor appointment inside our application. We are extremely fortunate to be trusted by our investors, such as the Bill & Melinda Gates Foundation, Singtel, UOB Ventures, Allianz, GoJek, Astra, Temasek and many more. We recently closed our Series C round and In total have raised around USD$180 million for our mission. Our team works tirelessly to make sure that we create the best healthcare solution personalised for all of our patient's needs, and are continuously on a path to simplify healthcare for Indonesia.

Siddharth Thakkar

Software Development Engineer - Android