Dependency Injection using Koin

Sreehari Vasudevan
6 min readMar 14, 2020

--

Koin is the dependency injection framework purely built in Kotlin. Comparing to Dagger2, we can find many features which are developer friendly in Koin. Learning curve for Koin is less comparing with Dagger2 😊 . There are many features which are debatable. As Koin is newbie and still growing, it would be unfair to compare with the giant!. Lets go through the implementation details of Koin.

Photo by Sara Kurfeß on Unsplash

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

Hmm.. 🤔 That’s sounds interesting, but does this really help a developer to focus on building application with ease? Los geht’s…

For those who are impatient , full source code can be found in Github.

Before proceeding, let’s get clarified how exactly does Dependency Injection(DI) helps development. DI is closely related to Single Responsibility Principle and Dependency Inversion.

  1. Single Responsibility Principle: Class / group of classes / module in a single program is responsible for specific functionality which resolves single problem.
  2. Dependency Inversion: Upper level modules should not depend on lower level modules.

If we follow these principles, program will have loosely coupled modules. Now lest see how to begin with!

Add dependencies in build.gradle with corresponding version

dependencies {
//Koin
implementation "org.koin:koin-android:$XX"
implementation "org.koin:koin-androidx-viewmodel:$XX"
}

Koin important keywords/functions

  1. startKoin : Create and register KoinApplication instance
  2. modules : Declare used modules
  3. androidContext : Use given Android context
  4. by inject() : allows to retrieve instances lazily
  5. get() : function to retrieve directly an instance (non lazy)
  6. koinComponent : For using koin features, tag the class with the same to get access to koin functions

Koin Scopes

  1. single : creates an object that persistent with the entire container lifetime
  2. factory : creates new object each time. No persistence in container
  3. scoped: creates object that persist to associated scope lifetime

All the dependencies needs to be Started in Application class with startKoin and providing Android context as per official documentation.

class MainApplication : Application(){override fun onCreate() {
super.onCreate()
initKoin()
}
private fun initKoin() {
startKoin {
androidContext(this@MainApplication)
modules(provideDependency())
}
}
//List of Koin dependencies
open fun provideDependency() = appComponent
}

We would need to consider Android Context and list of modules for initiating Koin. Now lets create common list for dependencies (appComponent). You can add all the required dependencies over here. In order to start with, let us consider Network and Utility class as targeted dependencies. Once we understood the steps and procedure for Koin, we can move on to more complicated dependencies. Altogether we will cover the following dependencies in this session.

val appComponent = listOf(
NetworkDependency,
AppUtilDependency,
UseCaseDependency,
viewModelDependency
...)

For Network dependency , create NetworkDependency.kt . As per Koin, all the dependencies should be described in individual Koin modules. For that , let us see how to create a Koin module. Then we will talk about how to start the same in Android. A typical module inculding Network dependency will be like the below.

Network Dependency

val NetworkDependency = module {

single {
Retrofit.Builder().addConverterFactory
(GsonConverterFactory.create
(GsonBuilder().create()))
.addCallAdapterFactory
(CoroutineCallAdapterFactory())
.baseUrl(BuildConfig.BASE_URL).build()
}
single { get<Retrofit>().create(SampleService::class.java)}
}

Don’t be panic, let me explain the details. As shown above, we will pass the required minimal details of Retrofit instance to be created. For instance, we need json converter to parse the response => GsonConverter. Next we need caller adapter factory. If using coroutines, we can use=> CoroutineCallAdapterFactory. If using Rx , then => RxJava2CallAdapterFactory can be used instead of coroutine one. Next goes the => base url for initial configuration. And then we need to tell the corresponding => API service required for Retrofit. In this case its the SampleService.kt. Being said that, the sample network service can be as follows.

interface SampleService{       
@GET("loginapi/")
suspend fun getRemoteData(@Query("parameter") param:String)
:DemoDataClass
}

With this, our Network Dependency is ready to rock! 😊

Utility Class

And next, let us cover the Utility class. In some cases , we require context in multiple methods. So to create the Utility class, we will need the context as a mandatory input, so that any method will be able to proceed with the same. Let us see how to create the same.

class AppUtility(var context: Context) {  //Use the context object as common reusable throughout methods.  //Utility method which needs context 
fun utilityMethodOne(){

}
}

Now for creating utility Koin dependency, the below will be the way. This will provide the context as well to the class while wiring the dependency. Koin will take care of setting up the context during the DI set up. We dont need to setup any explicit context. Simple isn't it! 😊

val AppUtilDependency = module {

single { AppUtility(androidContext()) }
}

UI (Base Fragment)

In-order to inject AppUtils in a Fragment, we can do the following.

class BaseFragment : Fragment(){   //Inject the app utility in base fragment
val mAppUtils: AppUtility by inject()
}

Now, as Utility is available in BaseFragment, other sub Fragments can access the app utility instance and methods.

Let us move on to classes which does not come under User Interface related classes. If any dependencies needed to be used in non UI related classes, we can use KoinComponent. As per Koin docs, you can find the below.

Once you have tagged your class as KoinComponent, you gain access to:

by inject() - lazy evaluated instance from Koin container

get() - eager fetch instance from Koin container

getProperty()/setProperty() - get/set property

Use Case

For Use Cases (as per Clean Code Architecture), we can have a base use case interface, which extends KoinComponent.

interface BaseUseCase : KoinComponent

Now for the specific use cases, extend this BaseUseCase class. As you can see, we are able to inject Utility cos of KoinComponent (inherited from BaseUseCase)

class LandingUseCase : BaseUseCase {   //Utility is available for usage in this class
val mAppUtils: AppUtility by inject()

fun someFunction(){
}
}

And now, we have to create koin dependency which can be initiated along with other dependencies.

val UseCaseDependency = module {
factory {
LandingUseCase()
}
}

View Model

For View models, we will create an abstract BaseViewModel , which will extend AndroidViewModel and KoinComponent

abstract class BaseViewModel(appContext:Application)
: AndroidViewModel(appContext), KoinComponent

Now, our LandingViewModel will inherit from BaseViewModel which requires a context to initiate. Hence we can create LandingViewModel as shown below.

class LandingViewModel(context:Application) : BaseViewModel(context) {
//Inject use case using Koin
private val landingUseCase : LandingUseCase by inject()
fun someFunction(){ }}

Landing Use Case will be able to be injected with the same pattern. Next, create ViewModel dependency which can initiate VM by passing context in the constructor.

val viewModelDependency = module {

viewModel { LandingViewModel(androidApplication()) }
}

With all the explained layers and concepts, we can start integrating required dependencies into our Fragment.

class LandingFragment : BaseFragment() {  //View model injection using Koin way
private val viewModel by viewModel<LandingViewModel>()

//Utility is available for usage in this class
val mAppUtils: AppUtility by inject()
override fun onViewCreated(view: View,
savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)

btnTwo.setOnClickListener {
viewModel.someFunction()
}
}
}

As you can see, invoking a function in View Model (got using dependency injection) from Fragment is fairly simple. And View Model’s method inturn invoking Use Case function as well.

Similarly you can try out including the other layers in Clean Code Architecture as well.

We have covered the dependency injection use cases in both UI and other classes which will be used in usual scenarios. Hope this helps.

Further read

For androidx.fragment , since 2.1.0-alpha-3 version FragmentFactory has got introduced to create instance of Fragment class. At start in KoinApplication declaration, use fragmentFactory() keyword to setup default KoinFragmentFactory instance.

startKoin {
androidLogger(Level.DEBUG)
androidContext(this@MainApplication)
androidFileProperties()
// setup a KoinFragmentFactory instance
fragmentFactory()

modules(...)
}

To declare Fragment instance, declare it as fragment in Koin module and use constructor injection

class MyFragment(val myService: MyService) : Fragment() {


}
val appModule = module {
single { MyService() }
fragment { MyFragment(get()) }
}

For host activity, setup fragment factory with setupKoinFragmentFactory()

class MyActivity : AppCompatActivity() {

override fun onCreate(savedInstanceState: Bundle?) {
// Koin Fragment Factory
setupKoinFragmentFactory()

super.onCreate(savedInstanceState)
//...
}
}

Further read portion is from official docs, More details can be found over here

Full source code can be found in Github here.

Do clap 👏 if you find this article helpful.

Thanks for Reading ! Happy Coding Cheers 🤜🤛🥂 🎉

--

--