Categories
android blog koin kotlin

Koin and arguments injection

In previous articles, we explored the basics of Koin. In this article let’s look at more advanced uses of Koin, in particular how to provide constructor dependencies.

Let’s say that we have a details view which needs a presenter, that presenter, in turn, needs an item id which can be used to request item details from the backend. In Koin, we can just provide a lambda with an argument instead of our dependency directly, for example:

class Presenter(val id : Long)
    val myModule = module {
         single { (id : Long) -> Presenter(id) }
    }

Now in our view (activity/fragment), we can just inject the presenter like this

val id = getIdFromArguments()
val presenter : Presenter by inject { parametersOf(id) }

What if we want to resolve a dependency directly in a Koin module for a particular class? We can use get() to eagerly load our dependency, for example:

val dataModule = module {
    single { UserSession() }
    single { UserRepository(get()) }
}
class UserRepository(private val userSession: UserSession)

In the example above UserRepository class depends on UserSession class and we can provide UserSession instance by simply writing get() which will walk our dependency graph in Koin and grab an instance of UserSession().

Now, can you spot a problem with the way we provide constructor arguments for UserRepository class? What will happen when we change the constructor for UserRepository class? The code above won’t compile! Why? Because we need to write another get() for the second argument, for example:

val dataModule = module {
    single { LocalStorage() }
    single { UserSession() }
    single { UserRepository(get(), get()) }
}
class UserRepository(private val userSession: UserSession,
                     private val localStorage: LocalStorage)

And this is the downside of Koin and the runtime dependency injection, Dagger can automatically write wiring for the new constructor during compile time, and we don’t have to modify anything. Fortunately this is a compile-time error and we will be warned by the Android studio promptly.

Stay tuned for the next article!

Categories
android blog koin kotlin

Koin and three ways to define a component (single, factory and scoped)

To continue where we left off in the last article, we will talk about Koin and different ways dependencies can be declared.

Singles

I use single the most when I declare my dependencies. It just means that there will be only one instance created for the entire app. Each injection will use the same instance. This is useful for repositories and long-living objects in the app. In a module, we can define a singleton this way

val myModule = module {
    // declare Service as single instance
    single { Service() }
    single { Controller(get()) }
}

Factory

In contrast to single, factory will create a new instance each time the component is injected, and this is useful when we inject presenters in the MVP architecture, or when we inject something that should be unique to the caller. To give an example:

class Controller()
val myModule = module {
    // declare factory instance for Controller class
    factory { Controller() }
}

Scoped

This is where Koin can shine. Imagine we need to keep our instances living a bit longer, so we can’t use factory but we also don’t want a singleton, so we can’t use single to create our dependencies. This is where scopes come in, we will tell Koin when we want to create a scope and from that moment on, all dependencies will live as long as the scope lives. This is useful when we want to have an auth scope and a non-auth scope to recreate our instances when a user logs in and logs out. This can help us reset our dependencies and provide new ones when the user state is changed. Think of a Retrofit instance, and how we have to inject the user token when the user is logged in so we need a new retrofit instance for that.

If anyone tried to set up scopes in Dagger they know how complicated and painful that can be. With Koin, it’s nothing like that, let’s look at an example

module {
    scope("scopeId"){
        scoped { Presenter() }
        // ...
    }
}

Here we just create a scope by name of scopeId and we can use that id to create and destroy that scope and all of its dependencies. We create our scope with this function

val scope = koin.createScope("myScope")

After we are done with this scope we can call

scope.close()

What? That easy? Of course, its Koin, we can do magic in just a few lines of code :).

Stay tuned for the next article in this series!

Categories
android blog koin kotlin

Using Koin for dependency management in Android apps

So I have been using Dagger2 in previous apps and I was quite ok with it. But I always felt that Dagger may be an overkill for small apps and even medium apps. So when I started working on a new Android app I decided to look for alternative ways to manage my code dependencies. I had a couple of ideas on what I need:

  • Good integration with the Android ecosystem
  • Easy to understand for new team members
  • Good performance

I heard about Koin quite some time ago so I thought this is the perfect opportunity to try it out. I did have one issue with Koin, give that it resolves dependencies during the run time I was worried about the performance a bit. So I tried to find some benchmarks and luckily I found what I was looking for in this blog post! Koin 2.x had a huge performance upgrade compared to Koin 1.x, almost a 10x improvement. For 400 dependencies it takes Koin 2.x around 10 ms to walk up the dependency graph and inject required dependencies which is more than enough for my current project.

Now comes the fun part, how easy is it to setup Koin on a new project? I have to be honest here, it took me no more than 20 minutes to get the basics of Koin and have a couple of dependencies injected in my activities. I was blown away by how simple Koin is. There are three basic concepts in Koin

  • Modules
  • Injection
  • Module list

Lets quickly explain each concept.

Modules

Similar to Dagger, Koin uses a module to group actual instances you will inject. So in my app, I have ServiceModule (dependencies for a background service), PresentationModule (android ViewModels) and DataModule (various repositories)

val myModule : Module = applicationContext {
    // ViewModel instance of MyViewModel
    // get() will resolve Repository instance
    viewModel { MyViewModel(get()) }
    // Single instance of Repository
    single<repository> { MyRepository() }
}

Injection

After instances are being provided in the modules there is a special inject() method that can inject those dependencies into Activities, ViewModels or Repositories. To give an example:

class MyActivity : AppCompatActivity(){
    // Inject MyPresenter
    val presenter : MyPresenter by inject()
    override fun onCreate() {
        super.onCreate()
        // or directly retrieve instance
        val presenter : MyPresenter = get()
    }
}

Module list

Before everything is tied up together, Koin needs to be started and all modules have to be connected so that Koin can start connecting them together. This is done with a special startKoin function that lives in the application class.

 startKoin {
            // use AndroidLogger as Koin Logger - default Level.INFO
            androidLogger()
            // use the Android context given there
            androidContext(this@MainApplication)
            // module list
            modules(serviceModule, presentationModule, dataModule)
        }

And that is basically it. In the next couple of articles, we will dig a bit deeper into some nuances that Koin has in store for us and how I solved some problems with injection in my current android project.

Stay tuned!