Published on: February, 2019
- Development
If you are a developer, even if you are just starting, at some point you must have had some nightmares handling dependencies in your code. What’s more, I’m sure that at that moment you remembered about the “loosely coupled” concept and wondered why you didn’t apply it, well it’s not that simple.
A loosely coupled system is one in which its components should have little or no knowledge of the implementations of other components. This reduces the risk of unexpected changes that a modification in a component can bring. Sounds pretty great, right? However, it is not that easy to apply in a real project.
In this context, dependency injection and service locator are design patterns that help us to keep our system loosely coupled by making a component independent of its dependencies. Moreover, there are already several frameworks that implement such patterns and there is plenty of documentation. Nevertheless, this article focuses on Android. Thus we will only discuss frameworks that work with Kotlin and Java, which are the adopted languages for this mobile operating system.
Since it would take a long time to review all the available frameworks, we will just look at the most popular options which are Dagger 2 and Koin. The former is a well-known library that works for Java and Kotlin, while the latter is a recent lightweight library written in and for Kotlin.
“Dependency injection is a style of object creation in which objects are created by an external entity or technique whereby one object supplies the dependencies of another object.” (Sinhal, 2017)
As Sinhal said, dependency injection helps us with the creation of objects, avoiding the dependency between classes. This technique reduces the options to write boilerplate code and also makes the development process smooth. Hence, some benefits of using dependency injection in our projects are (Sinhal, 2017):
Loose coupling: Your code depends less of other classes, so the fewer responsibilities a piece of code has, the less error prone it is.
Easily testable code: Because you are not creating an object in a method, you could pass mock objects to test your code.
Code reusability: When you implement dependency injection, you initialize your object once. Then, you can pass this object to whatever part of the code which requires it. This makes your code more reusable.
In order to start talking about Dagger, we have to go back a little bit in time to understand the necessity of building a new dependency injection framework for Android. Around 2011, Spring and Guice were, and probably still are, two of most popular dependency injection frameworks for Java. Moreover, both frameworks share some characteristics, among them, their configuration is executed at runtime. Such characteristics can make them extremely slow on Android due to VM differences with Java and mobile architecture constraints. As a consequence, teams at Square and Google wrote Dagger and Dagger 2 respectively, to use Java annotation processing to inspect class at compile time and write standard Java code automatically (Bowman, 2016).
When we use a dependency injection framework like Dagger, we give objects to an object. As an example, see in the snippet 1, there we give the window and the door objects to the house object. Thus, we satisfy the dependencies for the house object without creating them inside it.
Snippet 1
The principal components of dagger are modules and components. In order to include them in your code, you must use the annotations @Module and @Component (see Snippet 3).
Moreover, Dagger also provides some other useful annotations like @Inject and @Provides (we are talking about them in the next paragraph), also we have @Scope, @Named, @Qualifier, etc, that are not in this article but we will talk about them in the end.
class MainActivity : AppCompatActivity() { @Inject lateinit var info: Info override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) DaggerMagicBox.create().poke(this) text_view.text = info.text } } @Module class Bag { @Provides fun sayLoveDagger2(): Info { return Info("Love Dagger 2") } } class Info(val text: String) @Component(modules = [Bag::class]) interface MagicBox { fun poke(app: MainActivity) } |
Snippet 3
The code in the Snippet 3 is an example of how we can use Dagger in a simple way. First, we have the Info class that stores a string value. Second, the module class Bag returns an instance of Info in the function sayLoveDagger2. Never forget to add the annotation @Provides, because it sets the availability of the object for dependency injection. Also, the provides function cannot live alone, it has to be inside a module. Third, in the declaration of the variable info we have to use the @Inject annotation, so the Dagger framework can do its magic internally and avoid the object creation in the MainActivity. Finally, to “poke” (or inject) MainActivity into DaggerMagicBox (the dagger class created internally after you compile your project), the injection flow will be something like the next graph:
Graph 1. Injection flow in Dagger (Elye Project, 2018)
“Koin is a simple yet powerful dependency injection framework. It helps in making developers life easy by reducing a lot of boilerplate code which needs to be written in other frameworks like Dagger. Written in Kotlin, this is a small and simple library which makes dependency injection an easy task.” (Goyal, 2018)
Koin is totally written in Kotlin, and despite the fact that the library is not supported by Google, it is gaining followers at an incredible rate. According to its creators, Koin is a lightweight alternative to other more robust frameworks.
Strictly speaking Koin does not implement dependency injection, but the service locator pattern. Even though both patterns are very similar, there is a small difference. In contrast to the Snippet 1, see the Snippet 2 where we take objects from the house object in the service locator.
Snippet 2
class MainActivity : AppCompatActivity() { val infoHello : Info by inject { parametersOf("Hello Koin") } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) StandAloneContext.startKoin(listOf(appModule)) tvHello.text = "${infoHello.text}" } } class Info(val text:String) val appModule = module { single { (text:String) -> Info(text) } } |
Snippet 4
The Snippet 4 is the same example as the Snippet 3, the difference is that we are using Koin in the code above. First of all, the class Info is the same. Second, in Koin we have a global module (appModule), inside them we can have sub modules even though the reserved name will be the same (module). In this case, we are allowing the class info to receive a string parameter, if the Info class would not need a parameter the declaration just could be “single{ Info() }”. Moreover, we can notice the reserved word single, it tells to Koin that the Info class will only be created once. If you want to allow many instances of an object, we should use the word factory. Third, the declaration of the object infoHello is using “by inject”, and also inside the parentheses we have to write “parametersOf(“$parameter”)”, because we declare the Info object to receive parameters, if it were not like that, we can only declare the object like “Info by inject()”. Finally, we start Koin writing startKoin, usually this declaration is done in the Application class, since we are starting Koin in an activity we must use the StandAloneContext.
Dagger |
Koin |
Use annotations |
Do not use annotations |
Does not have a dedicated log |
Dedicated log for object creation |
Steep learning curve |
Low learning curve |
Errors can be found at compile time |
Errors are fired at runtime |
Not interaction with ViewModel |
Dedicated library to work with ViewModel (Android Jetpack) |
Support by Google |
Created by a French developer |
Written in Java |
Written in Kotlin |
Many posts in Stack Overflow |
Not many posts in Stack Overflow |
Table 1. A comparison between Dagger and Koin
Dagger and Koin are great dependency injection frameworks. Dagger has the advantage of being in the market for longer time than Koin. Nevertheless, Koin is earning adapters very quickly. If you are a Kotlin developer, it will be easier to start with Koin instead of Dagger. Although, Dagger has more information in blogs, articles, and questions in Stack Overflow. One drawback of Dagger is the fact that the library creates intermediate classes that increase the size of your apk. If you are worried about this, it is then a plus for Koin because it does not create intermediate classes.
Finally, we can say that Koin is a great alternative when it comes to small projects. In big ones there is a risk of overhead because Koin’s code generation will outpace the possible advantages. Thus, older and more mature frameworks, like Dagger, are recommendable when it comes to big projects with a distributed team. Koin however, seems like a great alternative for small and medium-sized apps (Stoltman, 2018). Finally, the decision will depend on how easy the chosen library adapts to your coding style and how comfortable you or your team feels at using it.
If we want to have many instances of an object in Dagger we can tag our instances by adding the annotation Qualifier, in Koin as we said before we could use the reserved word factory. In any case, in Koin if we want to tag our instances we can give names to our submodules. Furthermore, in Dagger if we want to have only one instance of an object we can use the annotation Singleton, something similar to Koin’s single reserved word. Additionally, for both libraries we have a scope functionality, in Koin we only have the reserved word scope and bindScope, on the other hand, in Dagger we have Scope, ActivityScope, and FragmentScope. For interested readers who want to review certain details that we do not cover like Scopes or Qualifiers in Dagger and Scopes in Kotlin, the following references could be useful to you: