Koin is a dependency injection library written in pure Kotlin

How to manage dependency injection with a time scope mechanism

For future students of the course “Android Developer. Professional” prepared a translation of a useful article.

We also invite you to take part in an open webinar on the topic “Writing a Gradle plugin”


What is this article about

You will learn how to use Koin modules to limit survivability of component-specific dependencies. You will also learn about the standard Koin scopes and how to work with custom scopes.

Introduction

Android developers do not recommend using Dependency Injection (DI) if your application has three or fewer screens. But if there are more of them, it is better to use DI.

A popular way to implement DI in Android applications is based on the Dagger framework. But it requires deep study. One of the best alternatives to this framework is Koin, a library written in pure Kotlin.

If you’ve used Dagger or any other DI library before, you probably know how important the scope mechanism is in this process. It allows you to determine in which cases you need to get the same dependent object, and in which – a new one. It also helps free up unclaimed resources and memory.

Areas in Koin

The concept of a realm in Koin is similar to that in Android. It allows, for example, to limit the scope of the view model (ViewModel) to a certain activity and use this model in the fragments that fill the activity.

There are generally three types of time zones in Koin.

  • single (single object) – an object is created that persists for the entire period of the container’s existence (similar to a singleton);

  • factory (object factory) – every time a new object is created, without saving in the container (sharing is impossible);

  • scoped (object in scope) – creates an object that persists within the lifetime of the associated time domain.

Single object.  Factory of objects (factory)
Single object. Factory of objects (factory)

View area single returns the same instance on every request, and factory returns a new instance each time.

Custom area

Standard Areas single and factory in Koin live during the life cycle of Koin modules. However, in real-world use cases, the dependency injection requirements will differ.

Dependencies are usually only needed for a specific period of time. For example the repository OnBoardRepository in Android app is only required when registering a user. Once the user is logged in, holding this repository in memory will be a waste of resources.

To achieve the desired behavior in Koin, you can use the timezone API. In the Koin module you can create string qualified scope and declare dependencies inside it using unique qualifiers. Let’s take it step by step.

Step 1

First, let’s create a module, declare an empty area, and give it a name. In this case, we have given the realm a name CustomScope… You can name it according to your requirements. This is how it looks:

creating custom koin scope

Step 2

The next step is to declare the required dependencies using scopes single and factory according to project requirements. The key is to assign unique qualifiers to scopes. Like this:

dependencies inside custom scopes

Step 3

We are done with the configuration in the Koin module. In this step, we need to create a region from the component from which we import the required dependencies. Scopes are usually created from Android components like Activity, Fragment, etc.

To create a scope, we first need to get an existing instance of the Koin bean and then call the createScope function, passing it the scope ID and name.

val stringQualifiedScope = getKoin().createScope(    
  "ScopeNameID", named("CustomeScope"))

Given a CustomScope as the value of the name parameter, Koin will look for the scope that we declared under that name in the Koin modules. ScopeNameID Is the identifier we use to distinguish one area from another. It is used internally as a key to find this area.

If you are accessing regions or creating them from multiple Android components, then instead of the function createScope it is recommended to use the function getOrCreateScope… It is obvious from the names of these functions what they do.

Step 4

Finally, we create an instance of the dependency we want to use. We did this using the area we created. Here’s what happened.

val sampleClass = stringQualifiedScope.get<SampleClass>(        
qualifier = named("scopedName"))

scopedName and factoryName Are the qualifiers we declared inside the Koin module in step 2.

Step 5

To get rid of the dependencies created by stringQualifiedScope, in particular sampleclass, you need to call the function close… For example, if you want to get rid of the dependencies created in this area when the activity is destroyed, then you need to call the function close within the appropriate method onDestroy… Like this:

override fun onDestroy() {
    super.onDestroy()
    stringQualifiedScope.close()
}

Koin-Android

The above is a general approach to limiting dependency survivability to a specific time domain. It can be used on any platform supported by Koin. As an Android developer, I would now like to combine the Koin realm and lifecycle realm mechanisms to minimize the work I have to do each time I create an activity.

To do this, you need to import the Koin-Android libraries. Add the following lines to the file dependencies node build.gradle application level:

// Koin for Android
implementation "org.koin:koin-android:$koin_version"
// Koin Android Scope features
implementation "org.koin:koin-android-scope:$koin_version"

Koin-Android Modules

Now, in order to reduce boilerplate code, we want, for example, to automatically close the area within the method onDestroy Android component. This can be done by linking Koin to the dependency import with lifecyclescope

First, you need to create a dependency area with Android components in the Koin module. How to do it:

val androidModule = module {

    scope<SampleActivity> {
        scoped { SampleClass() }
    }
  
}

scoping dependency with android activity

Then you need to inject the dependency into the activity using lifecyclescope:

val sampleClass : SampleClass by lifecycleScope.inject()

This will allow us to close the area when the activity is destroyed, saving us from manual operations. This is how our code looks like:

@OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)    
fun onDestroy() {
    if (event == Lifecycle.Event.ON_DESTROY) {       
        scope.close()
        }
    }
}

This approach will automate the work of creating areas, assigning qualifiers, and destroying areas. It seems that these are simple steps, they can be performed manually. But it’s important to automate repetitive work, and as the application evolves, this becomes obvious.

Additional materials

That’s all. I hope you learned something useful for yourself. Thanks for attention!


More about the course “Android Developer. Professional”. You can sign up for the open lesson “Writing a Gradle plugin” here.

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *