Android - Annotations
This tutorial lets you write an Android application and use Koin dependency injection to retrieve your components. You need around 10/15 min to do the tutorial.
Get the code
Gradle Setup
Let's configure the KSP Plugin like this:
apply plugin: 'com.google.devtools.ksp'
android {
sourceSets {
main.java.srcDirs += 'src/main/kotlin'
test.java.srcDirs += 'src/test/kotlin'
}
// For KSP
applicationVariants.configureEach { variant ->
kotlin.sourceSets {
getByName(name) {
kotlin.srcDir("build/generated/ksp/${variant.name}/kotlin")
}
}
}
}
Add the Koin Android dependency like below:
dependencies {
// Koin
implementation "io.insert-koin:koin-android:$koin_version"
implementation "io.insert-koin:koin-annotations:$koin_ksp_version"
ksp "io.insert-koin:koin-ksp-compiler:$koin_ksp_version"
}
Application Overview
The idea of the application is to manage a list of users, and display it in our MainActivity
class with a Presenter or a ViewModel:
Users -> UserRepository -> (Presenter or ViewModel) -> MainActivity
The "User" Data
We will manage a collection of Users. Here is the data class:
data class User(val name : String)
We create a "Repository" component to manage the list of users (add users or find one by name). Here below, the UserRepository
interface and its implementation:
interface UserRepository {
fun findUser(name : String): User?
fun addUsers(users : List<User>)
}
class UserRepositoryImpl : UserRepository {
private val _users = arrayListOf<User>()
override fun findUser(name: String): User? {
return _users.firstOrNull { it.name == name }
}
override fun addUsers(users : List<User>) {
_users.addAll(users)
}
}
The Koin module
Let's declare a AppModule
module class like below.
@Module
@ComponentScan("org.koin.sample")
class AppModule
- We use the
@Module
to declare our class as Koin module - The
@ComponentScan("org.koin.sample")
allow to scann any Koin definition in"org.koin.sample"
package
Let's simply add @Single
on UserRepositoryImpl
class to declare it as singleton:
@Single
class UserRepositoryImpl : UserRepository {
// ...
}
Displaying User with Presenter
Let's write a presenter component to display a user:
class UserPresenter(private val repository: UserRepository) {
fun sayHello(name : String) : String{
val foundUser = repository.findUser(name)
return foundUser?.let { "Hello '$it' from $this" } ?: "User '$name' not found!"
}
}
UserRepository is referenced in UserPresenter`s constructor
We declare UserPresenter
in our Koin module. We declare it as a factory
definition with the @Factory
annotation, to not keep any instance in memory (avoid any leak with Android lifecycle):
@Factory
class UserPresenter(private val repository: UserRepository) {
// ...
}
Injecting Dependencies in Android
The UserPresenter
component will be created, resolving the UserRepository
instance with it. To get it into our Activity, let's inject it with the by inject()
delegate function:
class MainActivity : AppCompatActivity() {
private val presenter: UserPresenter by inject()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
//...
}
}
That's it, your app is ready.
The by inject()
function allows us to retrieve Koin instances, in Android components runtime (Activity, fragment, Service...)
Start Koin
We need to start Koin with our Android application. Just call the startKoin()
function in the application's main entry point, our MainApplication
class:
// generated
import org.koin.ksp.generated.*
class MainApplication : Application(){
override fun onCreate() {
super.onCreate()
startKoin{
androidLogger()
androidContext(this@MainApplication)
modules(AppModule().module)
}
}
}
The Koin module is generated from AppModule
with the .module
extension: Just use the AppModule().module
expression to get the Koin module from the annotations.
The import org.koin.ksp.generated.*
import is required to allow to use generated Koin module content
Displaying User with ViewModel
Let's write a ViewModel component to display a user:
@KoinViewModel
class UserViewModel(private val repository: UserRepository) : ViewModel() {
fun sayHello(name : String) : String{
val foundUser = repository.findUser(name)
return foundUser?.let { "Hello '$it' from $this" } ?: "User '$name' not found!"
}
}
UserRepository is referenced in UserViewModel`s constructor
The UserViewModel
is tagged with @KoinViewModel
annotation to declare the Koin ViewModel definition, to not keep any instance in memory (avoid any leak with Android lifecycle).
Injecting ViewModel in Android
The UserViewModel
component will be created, resolving the UserRepository
instance with it. To get it into our Activity, let's inject it with the by viewModel()
delegate function:
class MainActivity : AppCompatActivity() {
private val viewModel: UserViewModel by viewModel()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
//...
}
}
Compile Time Checks
Koin Annotations allows to check your Koin configuration at compile time. This is available by jusing the following Gradle option:
ksp {
arg("KOIN_CONFIG_CHECK","true")
}
Verifying your App!
We can ensure that our Koin configuration is good before launching our app, by verifying our Koin configuration with a simple JUnit Test.
Gradle Setup
Add the Koin Android dependency like below:
// Add Maven Central to your repositories if needed
repositories {
mavenCentral()
}
dependencies {
// Koin for Tests
testImplementation "io.insert-koin:koin-test-junit4:$koin_version"
}
Checking your modules
The verify()
function allow to verify the given Koin modules:
class CheckModulesTest : KoinTest {
@Test
fun checkAllModules() {
AppModule().module.verify(
extraTypes = listOf(
SavedStateHandle::class
))
}
}
With just a JUnit test, you can ensure your definitions configuration are not missing anything!