Skip to main content
Version: 4.1

Android - Jetpack Compose

This tutorial lets you write an Android application and use Koin dependency injection to retrieve your components. You need around 10 min to do the tutorial.

note

update - 2024-10-21

Get the code​

Gradle Setup​

Add the Koin Android dependency like below:

dependencies {

// Koin for Android
implementation "io.insert-koin:koin-androidx-compose:$koin_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) -> Composable

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​

Use the module function to declare a Koin module. A Koin module is the place where we define all our components to be injected.

val appModule = module {

}

Let's declare our first component. We want a singleton of UserRepository, by creating an instance of UserRepositoryImpl

val appModule = module {
singleOf(::UserRepositoryImpl) bind UserRepository::class
}

Displaying User with UserViewModel​

The UserViewModel class​

Let's write a ViewModel component to display a user:

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

We declare UserViewModel in our Koin module. We declare it as a viewModelOf definition, to not keep any instance in memory (avoid any leak with Android lifecycle):

val appModule = module {
singleOf(::UserRepositoryImpl) { bind<UserRepository>() }
viewModelOf(::UserViewModel)
}

The get() function allow to ask Koin to resolve the needed dependency.

Injecting ViewModel in Compose​

The UserViewModel component will be created, resolving the UserRepository instance with it. To get it into our Activity, let's inject it with the koinViewModel() function:

@Composable
fun ViewModelInject(userName : String, viewModel: UserViewModel = koinViewModel()){
Text(text = viewModel.sayHello(userName), modifier = Modifier.padding(8.dp))
}
info

The koinViewModel function allows us to retrieve a ViewModel instances, create the associated ViewModel Factory for you and bind it to the lifecycle

Displaying User with UserStateHolder​

The UserStateHolder class​

Let's write a State holder component to display a user:

class UserStateHolder(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 UserViewModel's constructor

We declare UserStateHolder in our Koin module. We declare it as a factoryOf definition, to not keep any instance in memory (avoid any leak with Android lifecycle):

val appModule = module {
singleOf(::UserRepositoryImpl) { bind<UserRepository>() }
factoryOf(::UserStateHolder)
}

Injecting UserStateHolder in Compose​

The UserStateHolder component will be created, resolving the UserRepository instance with it. To get it into our Activity, let's inject it with the koinInject() function:

@Composable
fun FactoryInject(userName : String, presenter: UserStateHolder = koinInject()){
Text(text = presenter.sayHello(userName), modifier = Modifier.padding(8.dp))
}
info

The koinInject function allows us to retrieve a ViewModel instances, create the associated ViewModel Factory for you and bind it to the lifecycle

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:

class MainApplication : Application(){
override fun onCreate() {
super.onCreate()

startKoin{
androidLogger()
androidContext(this@MainApplication)
modules(appModule)
}
}
}
info

The modules() function in startKoin load the given list of modules

While starting the Compose application we need to link Koin to our current Compose application, with KoinAndroidContext:

class MainActivity : AppCompatActivity() {

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
MaterialTheme {
KoinAndroidContext {
App()
}
}
}
}
}

Koin module: classic or constructor DSL?​

Here is the Koin moduel declaration for our app:

val appModule = module {
single<HelloRepository> { HelloRepositoryImpl() }
viewModel { MyViewModel(get()) }
}

We can write it in a more compact way, by using constructors:

val appModule = module {
singleOf(::UserRepositoryImpl) { bind<UserRepository>() }
viewModelOf(::UserViewModel)
}

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:

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.verify()
}
}

With just a JUnit test, you can ensure your definitions configuration are not missing anything!