Koin Benefits in Big Projects

Iona Bartishvili
TBC Engineering

--

In TBC Business Mobile Bank the development team of the Android platform decided to use Koin in our project for the benefit of DI. So, we want to tell you the story of why we chose Koin.

Koin is a library in the Android world, which lets developers for dependency injection and inversion of control issues. but, the technology itself is based on Service Locator, which involves placing the object instances in the graph for later provision. The library is written in Kotlin and uses a lot of good features to provide solidity and simplicity.

Koin is the Simplest.

In TBC Business Mobile, we chose Koin for its simplicity not only for development but also for facilitating the pain during the process of onboarding junior developers.

understanding the basics of the Koin library and its features is incredibly easy, conversely with Dagger 2. It needs one module (in which we teach Koin how to provide some instance to us) and then get the instance with get() function. we can also use by inject() if we want the lazy initialization.

for example, if we want to provide XViewModel class, it’s very intuitive to use Koin’s feature, ViewModel scope.

We Define XViewModel class that has the dependency on ContactsDataProvider

then we teach Koin how to provide ViewModel instance. and provide ContactsDataProvider with get() function (which is already defined in DataProvidersModules with @single scope)

get() function provides ContactsDataProvider in order to pass this instance to ViewModels constructor
And then simply inject ViewModel instance with by viewModel<>()

In that way, Koin DSL helps us to code more clean and more human-understandably. with Dagger, we would have to write junk of modules and components for both the view model (it’s a bit of a “problem”) and Data providers.

Koin DSL

Koin is based on “Domain-specific language” which means more human-readable code consisting of several keywords (most basics)

  • module{ } — create a Koin module (or a sub-module)
  • factory{ } - provide factory definition (different instance every provision)
  • single{ } - provide a single definition (same instance every provision)
  • get() - get the instance from the Koin container
  • inject<>() - inject the dependency at runtime (lazy injection)
  • bind - type binding for a given definition

Named Definitions

if we ever will need to provide the same instance by Koin, the library will throw some exception, while the provided type definition already exists in the graph. it is most useful when we need to provide definitions with @single scope. for example:

and then getting via:

Scoping

Koin also supports us to use @single, @factory, @viewModel, and even custom scopes. scoped the definition creates an object tied to an associated scope lifetime. we can use both String and Type Qualified scoping.
we can also provide Android lifecycle-related scoping. for instance, if we want to scope some instance by our activity, we just simply code like:

after paying attention to lifecycleScope.inject() function, it provides closing the scope during onDestroy(), so that we don’t need to close it manually.

Fast in built-time

Koin doesn't use annotation processing or code generation. with that being said, it doesn’t prevent or slow down the build time, nevertheless, we have more than 500 definitions in the project.

we used 10 different android smartphones in order to test the speed and performance of loading modules and definitions. the approximate time spent on loading the definitions, was about 30–50 milliseconds while providing a basic instance between 0.25 to 1–2 milliseconds. for real scenarios, this might don't describe exactly (because Koin only will generate a definition when the instance is needed), but the ratio will be the same as on these tested smartphones.

Testing with Koin is even simpler

Testing with koin and Mockito is very easy since koin will provide us with mock modules before each test. we can initialize koin instance before any of our tests. after all the test is finished, simply close the koin container.

since we don’t have the object of the Context class, we can provide a mock object with Mockito.

we also have the opportunity to load koin modules separately for every individual test, using loadKoinModules() .

but, you may have to unloadKoinModules(listOfTestModules) in order to improve the test’s speed and not override the container.

Note: if you have some concrete definitions in listOfTestModules that is already defined in Koin’s container, you will have some exception. for that, you may have to write override = true parameter while defining a bean. make a provision, that you can also write this parameter on the root module, for instance:

val testModules = module(override = true){
factory {}...
}

No incomprehensible compile-time errors

Understanding errors in Dagger is a struggle. When considering the new team members, the project requires a very skilful developer (or a junior one, who is obliged to spend a lot of time learning this library) in order to understand and maintain dagger. This, of course, requires a lot of effort and recourses.

The downside of koin is that we get exceptions at runtime, so we might overlook and forget these issues. This might be very dangerous. However, in a good environment where all the development processes are straightened up, there is no possibility that these exceptions will harm the business. Plus, we can configure the linter software or make a little plugin in order to automate all these injection processes.

Koin for a big and long-term project

With all that said, Koin exceeds several problems regarding module classes. They might become God classes during the growing process of the project. for instance, if you want to have a lot of ViewModels, you will probably get the hundreds or even thousands of lines in ViewModelsModules class. or maybe you have hundreds of Interactors you want to inject. this problem will cause time to time while the project is getting bigger and bigger.

But, fortunately, Koin supports us to load modules dynamically. So, this might be a solution? why not give it a try?

thanks to loadKoinModules() providing us with the opportunity for loading definitions at runtime, dynamically.
So, let's examine the example of the maintenance screen. we have, MaintenanceScreenActivity , MaintenanceScreenViewModel (which has a dependency on MaintenanceServiceUsecase) and we need some koin modules to support the objects for both of the classes.

firstly, we need to define our modules in MaintenanceScreenModules.kt . we might need the parameter of the root module in order to avoid some exceptions from koin ( override = true ).

then we simply load koin modules, dynamically and define the entry point for those modules. So, let's take OnCteate() as an Entry Point. we’d load those in Oncreate and unload before ON_DESTORY happens. we clear those in Koin’s container in order to avoid memory leaks.

This is not the best practice ( I hope you don't do this kind of configurations in your project) but with some abstraction, It may become a lot better solution. maybe you use BaseX classes in order to get rid of duplicating the code consisting of loading and unloading the modules.

get(), get(), get()….

Some people have concerns about the design of the library. the number of get() keyword may become huge. Because of that, the maintenance of module classes becomes very hard and confusing from time to time. However, I don’t think that this should be a problem, because we can always use parameter names in the constructor and then assign the get() function. in that way, the code becomes more readable, intuitive and less confusing.

Moreover, when ViewModel has so much constructor parameters that we can’t get ourselves from the maze, maybe we should think, does this ViewModel abrogate the principle of single responsibility? In that case, do we need to split these ViewModels into fractions, so that every part has a single responsibility?

After all, I think that Koin has a very big potential to be the alternative to Dagger not only in small and medium-sized projects but even in big ones, so that’s why we prefer koin in the development team apart from Dagger 2.

--

--