Koin: Dependency Injection Simplified
Dependency injection is a way to increase unit test coverage in many applications, including Android apps. By injecting mocked versions of classes or interfaces that another class uses, it's much easier to test each potential code path.
How does this work in practice when developing for Android? In the past this usually meant implementing Dagger. While widely used and documented, it has a bit of a learning curve and can be a stumbling block at times.
Now, let's discuss Koin. It bills itself as the following:
A pragmatic lightweight dependency injection framework for Kotlin developers. Written in pure Kotlin using functional resolution only: no proxy, no code generation, no reflection!
Koin is a DSL, a lightweight container and a pragmatic API.
Since new Android apps at Rocket are built using Kotlin and Koin is built for Kotlin, it's a natural fit.
One useful benefit of Koin is that by using Kotlin's type inference, the initialization is very concise. As a result, take the following simple class:
class SomeClass(val someService: SomeService)
Then, when setting up Koin, just the get()
method is needed to find the appropriate class to inject:
class MyApplication : Application() {
override fun onCreate() {
super.onCreate()
startKoin(this, listOf(module {
single { SomeService() }
single { SomeClass(get()) }
}))
}
}
That's simple and easy enough to dive into classes to see what Koin is getting in each case.
Since we use Android Architecture Components at Rocket, does Koin do anything to help us there? It sure does! Let's create a ViewModel:
class SomeViewModel(private val someClass: SomeClass) : ViewModel()
To set up this ViewModel using Koin, we'd do the following, using viewModel
instead of single
:
class MyApplication : Application() {
override fun onCreate() {
super.onCreate()
startKoin(this, listOf(module {
single { SomeService() }
single { SomeClass(get()) }
viewModel { SomeViewModel(get()) }
}))
}
}
Now, when we want to use SomeViewModel
in our Fragments or Activities, that's easy enough as well:
class SomeActivity : AppCompatActivity() {
private val someViewModel: SomeViewModel by viewModel()
}
class SomeFragment : Fragment() {
private val someViewModel: SomeViewModel by viewModel()
}
For anyone who has used Architecture Components for a while, there are situations where a Fragment's ViewModel should be tied to the Fragment's lifecycle while at other times it should use a ViewModel associated with the Activity's lifecycle and shared. By just changing private val someViewModel: SomeViewModel by viewModel()
to private val someViewModel: SomeViewModel by sharedViewModel()
, then the Activity lifecycle is used instead.
As a result, Fragments and Activities no longer depend on using ViewModelProviders. Any lateinit
or nullable ViewModels are also handled with Koin's lazy loading.
These are just a few simple examples on how Koin can help with Android development, particularly making Architecture Component ViewModels more concise. Setup is quick and easy, and adding it to an existing application can be done relatively painlessly. In addition, it's simple to read and understand, meaning developers new to Android or the project can get up to speed quickly.