Blog Infos
Author
Published
Topics
,
Published
Topics
,

Dependency Injection is one of essential things in Object Oriented Programming. Dependency injection concept origins from fifth principle of S.O.L.I.D. The principle behind it states that a class should not configure (or create) it’s own dependencies but instead its dependencies should be created by other class from outside. By doing so, we are able to make a class not tightly coupled with its own dependencies that in the end will make a class easier to be unit tested with its own class responsibility.

Since Kotlin run on JVM, we can use one concept that already exists in JVM world to help us in building DI Library. To build dependency injection library in JVM world, we can use Java Reflection to construct dependencies of a class needed at runtime and inject into the class. First, I’m going to share a simple sample example of Android Dependency Injection which will be using MVP pattern for an UI Activity in Android. An Activity will have one presenter as its dependency to perform UI logics. For an example, we will name the activity as MainActivity and presenter as MainPresenter. MainPresenter will be an Interface and it will have its implementation in MainPresenterImpl. As this goes, MainActivity doesn’t need to know MainPresenter’s implementation. As principles we always hold in OOP, we should always code against an interface instead of its concrete implementation.

class MainActivity: AppCompatActivity() {
lateinit var mainPresenter: MainPresenter
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// inject mainPresenter here
mainPresenter.getData()
}
}
view raw di1.kt hosted with ❤ by GitHub

MainActivity.kt

interface MainPresenter {
fun getData(): String
}
view raw d12.kt hosted with ❤ by GitHub

MainPresenter.kt

class MainPresenterImpl: MainPresenter {
override fun getData(): String {
return "data"
}
}
view raw d13.kt hosted with ❤ by GitHub

MainPresenterImpl.kt

Here’s the interesting part, we will create an Injector class to help us in creating MainPresenter instance and wiring it into MainActivity. As this Injector class is ideally a class that can be reused and should only created once when our App is running, we will use Kotlin object keyword to help us in creating one instance of Injector (singleton) in our app.

object Injector {
fun inject()
}
view raw d14.kt hosted with ❤ by GitHub

Injector.kt

As stated above, we need to make a class that will list out all dependencies that our entry point needed during the runtime. In short, we will name the class that functions that way as a Module class. As our entry point is MainActivity , we can create a class MainModule that will help to provide all of the methods needed to create dependencies of MainActivity which is currently only MainPresenter (believe me things will get more interesting at the end of series). To make our Injector recognizes that MainModule is a Module class, we need to create an abstraction using interface to restrict in compile time that MainModule should implements InjectorModule.

interface InjectorModule {
}

InjectorModule.kt

class MainModule: InjectorModule {
fun provideMainPresenter(): MainPresenter {
return MainPresenterImpl()
}
}
view raw di5.kt hosted with ❤ by GitHub

MainModule.kt

As we already created a class that will help us to list out functions to construct all MainActivity dependencies, we can proceed to create logic that will run MainModule methods and set all dependencies instances into MainActivity field. In Java Reflection, we are able to list out all methods that a class defined by running this code Class<T>.getDeclaredMethods() in Java or Class<T>.declaredMethods in Kotlin. To make things safer during runtime, we should only execute methods that really functions as provider method that construct a dependency instance. In order to that, we need an unique identifier at the method that will tell us that it is a provider method. In this case, we will use annotation that will be placed above function that return a dependency instance.

@Retention(AnnotationRetention.RUNTIME)
@Target(AnnotationTarget.FUNCTION)
annotation class Provides
view raw di6.kt hosted with ❤ by GitHub

Provides.kt

class MainModule: InjectorModule {
@Provides
fun provideMainPresenter(): MainPresenter {
return MainPresenterImpl()
}
}
view raw di7.kt hosted with ❤ by GitHub

MainModule.kt

Now we can proceed in Injector class to run MainModule’s provider methods , cache it’s dependencies temporary and set it into MainActivityusing Field Reflection. Here’s the implementation.

object Injector {
fun <T : InjectorModule, R : Any> inject(kClass: KClass<T>, entryPointClass: R) {
// get all provider methods
val methods = kClass.java.declaredMethods
.filter { method -> method.isAnnotationPresent(Provides::class.java) }
// construct module instance
val moduleInstance = kClass.java.newInstance()
// construct and cache dependencies
val dependencies: MutableMap<Class<*>, Any> = mutableMapOf()
methods.forEach { method ->
dependencies[method.returnType] = method.invoke(moduleInstance)
}
// ToDo: inject dependencies
}
}
view raw di8.kt hosted with ❤ by GitHub

Job Offers

Job Offers


    Android Engineer

    American Express
    London
    • Full Time
    apply now

    iOS Engineer

    American Express
    London | Phoenix | New York
    • Full Time
    apply now

    Android Engineer

    ASOS
    London
    • Full Time
    apply now
Load more listings

OUR VIDEO RECOMMENDATION

, ,

Automated migration of Android apps to Bazel build system

Migrating large projects that consist of hundreds or thousands of modules and being maintained by a large team, from Gradle to Bazel might be challenging. I would like to discuss the process of automation of…
Watch Video

Automated migration of Android apps to Bazel build system

Pavlo Stavytskyi
Software Engineer
Lyft

Automated migration of Android apps to Bazel build system

Pavlo Stavytskyi
Software Engineer
Lyft

Automated migration of Android apps to Bazel build system

Pavlo Stavytskyi
Software Engineer
Lyft

Jobs

In short, basically we make sure we only received required provider methods by checking if a method does contain Provides::class.javaannotation that will be executed later on. Once we already get the provider methods, we can begin constructing the dependencies instance by looping and execute the provider methods. As we construct the instance, we need to save it into a Map using Class<*> as key. Class<*> key of MainPresenter is MainPresenter::class.java. As dependencies will only create once using Class<*> is the best option currently as key for caching. To run provider methods that construct the dependencies, we only need to run method.invoke(moduleInstance). method.invoke(moduleInstance) can accept argument using varargs but in our case now we can leave it like this because MainPresenter doesn’t have any dependencies yet. In this case, we need to provide moduleInstance which is instance of InjectorModule in our case it is MainModule to run the provider method. In the end, all instances of dependencies will be stored inside dependencies: MutableMap<Class<*>, Any> map. Now we’re left with injecting part.

Here comes the part of entryPointClass: R in inject method’s arguments. It plays an important part of injecting dependencies into a public field of a class. To get all fields of a class using Java Reflection, we can use Class<T>.fields method in Kotlin. But we try to run the code, we are getting literally all fields that an Activity contains. You will see result like this:

These aren’t great for us as we only need the fields that we’re going to inject the dependencies of MainActivity. To solve this problem, we will use the same approach as provider method of Module which is using Annotation as field identifier. In this case, we can make Inject annotations that we will place above fields of MainActivity that we will inject.

@Retention(AnnotationRetention.RUNTIME)
@Target(AnnotationTarget.FIELD, AnnotationTarget.CONSTRUCTOR)
annotation class Inject
view raw d19.kt hosted with ❤ by GitHub

In current case, we only need AnnotationTarget.FIELD , but we’re adding AnnotationTarget.CONSTRUCTOR also for more complex DI case that we will discuss later on. After we’re done creating the Inject annotation, we can proceed to place @Inject above fields that are dependencies of MainActivity.

class MainActivity: AppCompatActivity() {
@Inject
lateinit var mainPresenter: MainPresenter
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// inject mainPresenter here
mainPresenter.getData()
}
}
view raw di10.kt hosted with ❤ by GitHub

Now the fields are annotated then we can proceed to inject dependencies from our cached into entryPointClass using Field.set(entryPointClass, dependencyInstance) .

object Injector {
fun <T : InjectorModule, R : Any> inject(kClass: KClass<T>, entryPointClass: R) {
// get all provider methods
val methods = kClass.java.declaredMethods
.filter { method -> method.isAnnotationPresent(Provides::class.java) }
// construct module instance
val moduleInstance = kClass.java.newInstance()
// construct and cache dependencies
val dependencies: MutableMap<Class<*>, Any> = mutableMapOf()
methods.forEach { method ->
dependencies[method.returnType] = method.invoke(moduleInstance)
}
// Inject dependencies
entryPointClass.javaClass.fields.filter { field ->
field.isAnnotationPresent(Inject::class.java)
}.forEach { field ->
// field.type will return Class<*>
field.set(entryPointClass, dependencies[field.type])
}
}
}
view raw di11.kt hosted with ❤ by GitHub

Then we wire up the Injector into onCreate method of MainActivity.

class MainActivity: AppCompatActivity() {
@Inject
lateinit var mainPresenter: MainPresenter
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
Injector.inject(MainModule::class, this)
mainPresenter.getData()
}
}
view raw di12.kt hosted with ❤ by GitHub

Voila ! There goes our simple Dependency Injection Library using Injectorand it’s custom annotations. But we’re not done yet, we will face another DI issue. Issues are what if MainPresenter does have another dependency like this:

 

MainPresenterImpl.kt

 

Using method.invoke(instance) will not work as we need to supply another dependencies into the method parameter that will turn out like this at MainModule.

 

MainModule.kt

 

Which means, we are going to create logic in Injector that are able to construct dependency instances that have another dependencies. It can be visualized as a tree of dependencies that are going to depend on one another. In that case, we will construct DI tree and all of dependency instances using DFS (sounds interesting). We are going to take up the challenge in the Part 2 of this DI Library series. For spoiler, you can checkout the solution at my Github here.

Thanks for reading. Stay tuned for my next article.

Full Source code: https://github.com/WendyYanto/kotlin-dependency-injection-lib

My Github : https://github.com/WendyYanto

YOU MAY BE INTERESTED IN

YOU MAY BE INTERESTED IN

blog
It’s one of the common UX across apps to provide swipe to dismiss so…
READ MORE
blog
Hi, today I come to you with a quick tip on how to update…
READ MORE
blog
Automation is a key point of Software Testing once it make possible to reproduce…
READ MORE
blog
Drag and Drop reordering in Recyclerview can be achieved with ItemTouchHelper (checkout implementation reference).…
READ MORE

Leave a Reply

Your email address will not be published.

Fill out this field
Fill out this field
Please enter a valid email address.

Menu