Koin — библиотека для внедрения собственных зависимостей Kotlin
Вы узнаете, как оценить зависимости, относящиеся к конкретному компоненту, с помощью модулей Koin. Вы также узнаете об области действия по умолчанию для коинов и о том, как работать с настраиваемой областью действия.
Вступление
Как рекомендует команда Android, если ваше приложение содержит три или меньше экранов, вы можете обойтись без DI. Но при наличии более трех экранов всегда рекомендуется использовать DI.
Один из популярных способов реализации DI в приложениях Android — это фреймворк Dagger. Но реализация Dagger требует глубокого обучения. Одна из лучших альтернатив — Koin, изначально построенная на Kotlin.
Если вы использовали Dagger или любую другую библиотеку DI, вы, вероятно, знаете, насколько важна область видимости. Это позволяет нам определить, получаем ли мы тот же зависимый объект или новый. Это также помогает высвободить ненужные ресурсы и освободить память.
Области применения в коине
Область действия в Koin аналогична области действия в Android, например, определение области видимости модели представления для определенного действия и использование ее во фрагментах, увеличенных в этом действии.
Из коробки у Koin есть три вида прицелов:
- single : Создайте объект, который будет постоянным в течение всего времени существования контейнера (аналогично синглтону).
- factory : каждый раз создавайте новый объект — без сохранения в контейнере (нельзя делиться).
- scoped : Создать объект, который постоянно привязан к времени существования связанной области.

Пользовательская область
Single и factory — это объемы по умолчанию от Koin, которые живут в течение всего срока службы модулей Koin. Однако наши требования не будут такими же в сценариях использования в реальном времени.
Обычно нам нужны зависимости только для определенного периода времени. Например, в приложении Android OnBoardRepository требуется только тогда, когда пользователь подключается. После того, как пользователь вошел в систему, удерживать его в памяти — пустая трата ресурсов.
Чтобы добиться такого поведения в Koin, мы можем использовать API области видимости. В модуле Koin мы можем создать String Qualified Scope и объявить зависимости внутри области с уникальными квалификаторами. Давайте сделаем это шаг за шагом:
Шаг 1
Сначала создайте модуль, объявите пустую область и дайте ей имя. В данном случае это CustomeScope , вы можете назвать его в соответствии с вашими требованиями. Взглянем:
Шаг 2
Следующим шагом является объявление необходимых зависимостей с использованием single и factory в соответствии с требованиями. Ключевым моментом здесь является присвоение им уникальных квалификаторов. Взглянем:
Шаг 3
Мы закончили настройку в модуле Koin. На этом этапе нам нужно создать область, из которой мы импортируем эти зависимости. Обычно это компоненты Android, такие как Activity , Fragment и т. Д.
Чтобы создать область видимости, сначала нам нужно получить существующий экземпляр компонента Koin, а затем вызвать функцию createScope , передав ScopeID и имя области. Взглянем:
Задавая CustomeScope в качестве имени, Koin ищет в своих модулях область видимости, которую мы объявили с этим именем. ScopeNameID — это ScopeID, который мы можем использовать для идентификации области от одного к другому. Он используется внутри как Ключ (ID) для поиска этой области.
Если вы получаете доступ или создаете области из нескольких компонентов Android, рекомендуется использовать функцию getOrCreateScope , а не createScope . В объяснениях нет необходимости, поскольку мы узнаем их по именам.
Шаг 4
Наконец, на этом этапе мы должны создать экземпляр зависимости, которую мы хотим использовать. Мы сделали это, используя созданный нами объем. Взглянем:
scopedName и factoryName — квалификаторы, которые мы объявили внутри модуля koin на шаге 2.
Шаг 5
Чтобы избавиться от зависимостей, созданных с помощью stringQualifiedScope , например sampleclass , нам нужно вызвать функцию close . Например, если вы хотите избавиться от зависимостей, созданных в этой области при уничтожении активности, нам нужно вызвать функцию close в onDestroy . Взглянем:
Коин-Android
Вышеупомянутый подход к ограничению зависимостей определенной областью видимости является общим и может использоваться на любой платформе, поддерживаемой Koin. Но теперь, как Android, я хотел бы совместить область действия Koin с областью жизненного цикла, чтобы минимизировать работу, которую мне приходится выполнять каждый раз, когда создается новое действие.
Для этого вам необходимо импортировать библиотеки Koin-Android. Включите следующие строки в узел зависимостей в файле build.gradle уровня приложения:
Теперь мы хотим сократить шаблонный код, например закрыть область в функции onDestry компонента Android. Мы можем сделать это, связав импортируемые зависимости Koin с помощью lifecyclescope .
Во-первых, нам нужно создать область зависимостей с компонентами Android в модуле Koin. Взглянем:
Затем в действии нам нужно внедрить зависимость, используя lifecyclescope . Взглянем:
Это означает, что когда действие будет уничтожено, оно закроет область действия без необходимости делать это вручную. Взгляните на код:
Такой подход автоматизирует работу по созданию области, квалификации и уничтожению их. Это может показаться простым, но очень важно автоматизировать повторяющуюся работу, и вы поймете это, когда приложение начнет расти.
Koin — Simple Android DI
![]()
Dealing with dependency injection is one complex concept that I managed to understand before coming into the JVM space. I say this because Dagger-2 had complicated the concept for me when I came into android. Understanding how to arrange your code to accomplish what you wanted, took a lot of research and re-explaining. That said, Dagger-2 really did the job when you finally got how it worked.
I want to talk about another dependency injection framework that I recently adopted and how it has really simplified the process for me. Koin is a lightweight dependency injection framework, that uses Kotlin’s DSLs to lazily resolve your dependency graph at runtime.
If you are unfamiliar with the concept of dependency injection, you can read this article to understand what it is and how it is used. I am going to walk you through the key concepts of Koin, how to setup DI for Android and also Unit tests/Automated UI tests.
First get the library into your project. You will need to add the following to your module-level build.gradle.
I will mention some comparisons with Dagger-2 if you are already familiar with it, and if you are not, don’t worry, just ignore those parts.
Koin is a DSL, using Kotlin’s DSLs, that describes your dependencies into modules and sub-modules. You describe your definitions with certain key functions that mean something in the Koin context.
I will explain the key functions later in this post, but first, you should know that after you have described your dependencies in Koin modules, you need to call the startKoin function passing Android Context and your list of modules. For example:
With Koin, setting up dependency injection is kind of a manual process, you essentially plug your services into the module graphs and those dependencies will later get resolved at runtime. Here are the key functions.
- module < >— create a Koin Module or a submodule (inside a module)
- factory < >— provide a factory bean definition
- single < >— provide a bean definition
- get() — resolve a component dependency
- bind — additional Kotlin type binding for given bean definition
- getProperty() — resolve a property
Module
The module definition is very similar to Dagger-2 @Module definitions, it serves as a container for a collection of services that should be provided at runtime. You put all the services that a module should provide in the module block. For example:
Here we have a couple of things that I will like to address, the first thing is the module block after the = sign. This block is the container in which you will map out the services that this module provides, like in the code above, the data module provides a SharedPerferenceManager, a ContactManager, and a ContactServerManager. The other thing you will notice is that the module block is assigned as a value, this is because the Koin framework uses a list of modules to build the dependency graph, as you will see later in this post.
Factory
A factory serves the purpose of qualifying a dependency, it tells the Koin framework not to retain this instance but rather, create a new instance of this service anywhere the service is required. This can be done in a couple of ways:
First, ignore the single block, for now, we will discuss that next, and just focus on the factory areas.
There are a couple of things here to take note of, the first is the version of the factory function that just takes the generic parameter, that is an experimental feature that simplifies the code-block version. In that version, Koin will resolve all the dependencies of SomeClass, so that can be used for situations where you have already specified required dependencies for SomeClass in one of your Koin modules.
Koin also allows you to manually build the dependency, this can be used for cases where you want to build a dependency based on configurations or other criteria. This is what the code-block version of the factory does, you see first I create a new date and then I pass it as a constructor argument to create the SomeClass instance, which I return.
You also have situations where you want to provide an implementation for an interface, you can also do that using the automatic resolve with factoryBy, or you can manually build the dependency and specify the interface as the generic type parameter.
Single
The single definition does the exact opposite of the what factory does. It tells Koin to retain the instance and provide that single instance wherever needed. This can be done in some ways similar to the factory defined above. It is similar to Dagger-2 @Singleton annotation.
Similar to the factory above, there are a couple of ways to define a single bean. Both the experimental features that just take type parameters and the one that you create and return within your lambda block.
Get
The get function is a generic function that is used to resolve dependencies where needed. You use it to resolve any particular dependency that you need and it could be used as follows.
Bind
The bind is an infix function helps resolve additional types, what this means is, you create one bean definition and use the bind to resolve it as an additional type, for example:
The one thing to note here is that the single bean definition for SomeClass also binds to interfaces SomeInterface and Flammable, meaning with that single definition, you can provide services for the corresponding interfaces wherever required.
As you see when I try to resolve SomeInterface and SomeClass in the activity, the same definition is used to resolve the two. That’s the feature of the bind function.
Note: This does not only apply to single bean definitions it can also be used for factory definitions but in that case, a new instance will be created each time either of the types (SomeClass, SomeInterface or Flammable) is required.
Read or Write Properties
The dependency container can contain configuration properties or values that can be read and set at runtime. This is done using the getProperty or setProperty functions. These properties are in a key-value pair container, you read and write using a key, and specifying the value to set, or the type to read. for example:
A Little More (And Some Advanced Stuff)
There are some other things within the Koin framework, some are advanced some are just other aspects of Koin you should take note of.
Scope
Koin’s scope allows you to define local scoped-dependencies that are short-lived, like a Presenter, View Adapter, or other service components that are meant to be short-lived within a defined scope, like an Activity’s scope. This is similar to Dagger-2 @Scope annotation.
With the scope definition, you must specify the scope_id which is just a unique string representing the scope definition. The specified scope_id can then later be used as a reference, in creating, requesting or closing a scope. Here is what it looks like:
KoinComponent
The Koin component serves as the container context that allows you to interact with the Koin framework after you have defined your dependencies and started Koin (with the startKoin function), you get access to functions like get, inject, getKoin, viewModel, etc.
What I mean is, when you have started Koin and it has created its dependency container, to retrieve dependencies or interact with Koin, you would need to implement KoinComponent, or be a configuration-aware Android Component (i.e. Activity, Fragment, ContentProvider or Service). For example:
The first thing to note is, SomeActivity doesn’t implement KoinComponent but it has access to the Koin container, this is because Koin already provides extension functions for Android configuration-aware components, so you can access the Koin context with an Activity or the like.
Another thing to take note of is the by inject function that is used. You might be wondering what differentiates it from the get function. The difference is, get is eagerly evaluated, that is, when Object creation gets to the field initialization phase, SomeOtherService will be retrieved from the dependency container, but SomeLazyService will not be resolved until the class tries to use it, because it is lazily evaluated, that is why the by keyword is used.
The last thing is the BroadcastReceiver. As you see it has to implement KoinComponent to interact with Koin.
ViewModel
Since google provided us with architecture components, it has been a ride with the MVVM architecture. Koin adds to such a ride by providing viewModel definition, and this enables you to define a viewModel dependency. Here is what it looks like:
Parameters for Definitions
When you try out Koin you will notice that module, factory, and single take optional parameters which are:
- path: String — for a Koin module or a sub-module (inside a module)
- name: String — for a Koin factory or single bean definition
- createOnStart: Boolean — for a Koin single or module definition
- override: Boolean — for all definitions
Path
The path parameter is for the module and sub-module definitions, it serves as the location reference for particular dependencies or sub-module segments. The path could be used to retrieve defined dependencies. Here is what it looks like:
Note that the root module has path is serves as the empty string, then the reset represent the nested modules. And it can be used to retrieve dependencies like so:
One thing to notice is that, when trying to retrieve dependencies, the module paths are separated by dots, and the module paths come before the ClassName.
Here is how to look at this, look at the modules and sub-modules as java packages, and the single or factory dependency definitions as java classes inside java packages.
Since you are most likely familiar with Java packages and java files, you know that packages are just nested folders with java files, and when you want to reference some other java class you would use the package path and the ClassName (e.g. import com.your.domain.ClassName).
This same analogy goes for Koin modules and bean definitions, as you can see in the example above, to reference the SampleSecondService in the staging module, I used the path starting from the root module up until the module containing the dependency followed by the class name.
Note: Instead of using the eager resolution, get, you could also use the lazy resolution with the inject function. For example:
Name
The name parameter is used to override the default name that Koin gives each bean definition.
By default, Koin uses the module path combined with the class name, i.e. first_module.second_module.ClassName. If there are no module paths, Koin defaults to just the beans Class Name. For example:
In the example above the default name is used for the first definition, you can decide to not pass a name parameter to the get function, as that is what its default implementation. But for the second definition, you must use the same name specified during bean definition when trying to retrieve the dependency.
CreateOnStart
The createOnStart parameter is used to set eager creation of dependencies, and it can be only used on single and module definitions, this flag tells Koin to immediately create all single definitions when the startKoin function is called. Here is an example:
The createOnStart flag can be very helpful when you have some service(s) that might take time to resolve at runtime, it tells Koin to resolve those dependencies immediately when started and thus it could reduce or prevent lags when the user moves through the app.
Override
The override flag is used to override bean definitions, the flag marks any definition as overridable. This flag is very useful when you want to setup test doubles for your dependencies. So you can mark your test modules with the override flag and then it will override whatever existing services you previously set up. Here is what it looks like:
With the definitions you see that I have already provided dependencies that will run in production, so to substitute them, I will need to provide definitions marked with ‘override = true’.
Think of it like this, when you have a java class with methods (member functions), to override the behavior of any member function you mark your overriding function with the @Override attribute. This is kinda what the override = true flag is doing, you are marking your definitions to override pre-existing definitions.
Unit Testing
Of course, you need to write tests, unit tests should have been the first thing I talked about but out of the convention, I put it last.
When writing unit tests, you could specify the modules at runtime, and call startKoin within your test functions. There are just two things you need to do first and then you can start testing right away.
First, add the test library as a dependency in your module level gradle file.
Second, make sure that your test class implements KoinTest interface.
With this example above, you see that the test class implements KoinTest, and that is what gives you access to functions like get, inject, startKoin, getKoin, etc. Another thing that you would notice is that I can call startKoin without passing an Android Context.
Another helpful thing is that Koin gives a method that helps check that your dependency graph is well set up, so it will try and resolve all dependencies one by one in your graph.
Note that I am overriding the Application and Context classes because, these classes are not available in a unit test, so you have to override them with mock implementations. And you have to do this for any component that requires an Android Device to be set up.
Android Dependency Injection with Koin
Dependency Injection, or DI, is a design pattern/technique as old as time itself. You’ve probably heard it a couple of times during your career and it always comes up in the list of «good practices» when developing Android apps, but why is that?
Let’s first understand a bit of what DI is and how it was done in Android and then we can have a look at how Koin approaches the idea.
What is DI?
Dependency Injection consists in simply providing the dependencies needed by an object without creating them within the object itself. As the name says we’ll inject that dependency somehow into the object. I know, it sounded a bit confusing, let’s see an example:
Let’s say you have a repository class in your app that you can use to access your api service. You could create your class as such:
Exit fullscreen mode
This however imposes a strong coupling between the service class and the repository, you’re creating the service within the repository. How would you go about unit testing the Repository? You can’t test just the repository because the service is there and you can’t control how it behaves. Dependency Injection favors and helps us to create a mindset to enable writing testable code. We could modify the code and provide the service dependency when we create the repository, through the constructor:
Exit fullscreen mode
Now when we try to test the repository we can provide a mock instance of the service and we can correctly unit test the repository. This is still not DI per se, but rather an approach to coding that enables us to do it later. We can write code that makes DI easier (and makes testing easier) providing dependencies in various manners:
Constructor Injection: This is the preferable way of organizing your code, we should try to always provide dependencies at the moment of creation of an object. This however can become a bit messy if there are a large number of dependencies.
Field Injection: This type of injection requires some type of harness that will populate the field with the desired dependency. This makes testing a bit harder, but if we make use of a DI framework (more soon) we can choose how this field is populated and we can provide mocks to be injected in them.
Method Injection: We can make use of a setter method to provide the right dependency. By using a setter method, however, we can end-up with an object that is in an invalid state.
What is a DI Framework
A DI framework is nothing more than a tool to help you organize how DI is done in your code. Depending on the framework you’ll need to follow some steps in order to create a new class. By using a DI framework we can leverage from both constructor and field injections in a more organized way that enables us to develop a more decoupled and testable code. A very famous DI framework used in Android is Dagger, more specifically Dagger2 the latest version of it. About Dagger2 I have one thing to say:
The learning curve for Dagger2 is pretty steep and you can find many series online explaining how to setup Dagger2 in your project. The first configuration is always the toughest, after that Dagger is easily extendable and adding new dependencies is as easy as adding arguments to a method. For the purposes of learning how to apply Koin you’d need some idea of how Dagger works, specially the idea of Modules and Dependency graph.
- Module: A module is a central place where you can tell the framework how to create new instances of a dependency. You can add a variety of configurations in this module to help the framework decide how the new instance will be created. This is how a module looks like in Dagger2 for instance:
Exit fullscreen mode
- Dependency graph: It’s an «imaginary» (in memory graph) that the framework uses to keep track of where the dependencies are injected.
This is Koin as stated by the creators:
A pragmatic lightweight dependency injection framework for Kotlin developers. Written in pure Kotlin using functional resolution only: no proxy, no code generation, no reflection!
Here’s some differences between Koin and Dagger:
- Dagger2 is written in Java, Koin is purely kotlin so it looks more natural when used with Kotlin.
- Koin works as a Service Locater masked as Dependency Injection (if that makes any sense)
- Koin does not generate code == smaller build time
- Koin requires more manual configuration as opposed to using Annotations like Dagger2.
This all means a nice kotlinesque alternative to Dagger2. This doesn’t mean we should start replacing Dagger with Koin in every project we start from now on. Dagger is very powerful and once you get past the initial setup, it’s quite easy to use. Koin is just another option to managing dependencies in a nice organized way, if you’ll use it or not it will depend on your needs.
To add Koin into your project just add the dependencies to your app level build.gradle file:
Exit fullscreen mode
Usage and Configuration
The project I’ll be using to demonstrate the use of Koin can be found here. It’s a simple app that shows the top rated movies from the TMDB API. Here’s a peek of how it looks like:
After I’ve setup most of the classes in my code (keeping in mind the DI mindset) I’ve started writing my modules. I’ve started with the network module where I keep all network related injections.
Some observations from the gist:
- means we need this instance to be a singleton. If you’d like to create a new instance every time the dependency is needed you can use «factory» instead.
- When defining how/what type of instance you’d like koin to create you can specify a name and therefore have a named instance. This exists in Dagger through the @Named annotation and is specially useful if you have many instances of the same type.
- If you’d like Koin to provide an instance for you when creating another instance you can use the get() method and pass an optional name if it is an named instance. Koin whill locate the instance and inject it when creating this new object.
I’ve also created a separate module to concentrate all «Movie» related object creation:
In this gist you can see one of the nicest things about Koin. It has support for Arch Components, specially ViewModel. When I tried to use Dagger2 and ViewModel I had to inject a ViewModelFactory and from that create my ViewModel, in here we need only to declare it as a and it will be easily injectable in our activity:
Exit fullscreen mode
If your object is not a view model it can easily be injected as well, just use either inject() or get():
Exit fullscreen mode
The last piece of configuration that we need to do is to initialize Koin. This needs to be done in the onCreate method from your Application class:
Exit fullscreen mode
And simple as that all pieces are connected and you’ve created an easily testable app with decoupled dependencies 🙂
Koin – Dependency Injection for Kotlin
Koin is a lightweight dependency injection library for Kotlin and written in pure Kotlin. using functional resolution only: no proxy, no code generation, no reflection. Koin is a DSL, using Kotlin’s DSLs, that describes your dependencies into modules and sub-modules. You describe your definitions with certain key functions that mean something in the Koin context.

In this article, we are going to pass through the basic steps to setup and take advantage of dependency injection with Koin for a simple project.
1. Setup
2. Declare a module
Defines those entities which will be injected in the app.
3. Use Application layer to initialize Koin injection
Trigger the DI process and indicate which modules will be available when needed
4. Use the actual injection
This was the basic usage of koin which has much more power/features available in its framework. Another great Koin feature is the integration with the Architecture Component View Model. Koin has a specif project called koin-android-viewmodel project is dedicated to inject Android Architecture ViewModel.
Architecture Components with Koin: ViewModel
1. Setup
2. Declare a module for your viewModule
3. Use the actual injection
Koin Key Functions
- module < >— create a Koin Module or a submodule (inside a module)
- factory < >— provide a factory bean definition
- single < >— provide a bean definition
- get() — resolve a component dependency
- bind — additional Kotlin type binding for given bean definition
- getProperty() — resolve a property
Futher Details
I highly recommend the official docs and the following articles: Koin — Simple Android DI and Ready for Koin 2.0 by Arnaud Giuliani