Skip to main content
Version: 4.2

Android ViewModel

In this tutorial you build an Android app that greets a user picked from a list. You learn how to declare a ViewModel in a Koin module, inject it in an Activity, and start Koin from your Application class. It takes about 10 minutes and needs Android Studio.

This tutorial uses the Compiler Plugin DSL. For the same app with annotations, see Android & Annotations. To compare the styles, see Choosing your style.

Get the code​

The full sample is in koin-getting-started/android. Clone the repository and open the android folder in Android Studio.

Gradle setup​

The sample applies the Koin Compiler Plugin and adds koin-android, which includes the ViewModel support. Versions live in a version catalog:

# gradle/libs.versions.toml
[versions]
# Kotlin, Android Gradle Plugin and AndroidX entries omitted
koin = "<KOIN_VERSION>"
koin-plugin = "<KOIN_PLUGIN_VERSION>"

[libraries]
koin-android = { module = "io.insert-koin:koin-android", version.ref = "koin" }
koin-test-junit4 = { module = "io.insert-koin:koin-test-junit4", version.ref = "koin" }
koin-android-test = { module = "io.insert-koin:koin-android-test", version.ref = "koin" }

[plugins]
koin-compiler = { id = "io.insert-koin.compiler.plugin", version.ref = "koin-plugin" }
// app/build.gradle.kts
plugins {
alias(libs.plugins.android.application)
alias(libs.plugins.kotlin.android)
alias(libs.plugins.koin.compiler)
}

dependencies {
implementation(libs.android.appcompat)
implementation(libs.koin.android)

testImplementation(libs.koin.test.junit4)
testImplementation(libs.koin.android.test)
}

The Compiler Plugin needs Kotlin 2.3.20 or later. For current versions and the BOM, see Gradle setup. For plugin options, see Compiler Plugin setup.

The application​

The Activity asks a ViewModel for a greeting. The ViewModel uses UserService, which reads from UserRepository:

User -> UserRepository -> UserService -> UserViewModel -> MainActivity

Let's write the user data​

Start with the data class and a default list of users:

data class User(val name: String, val email: String)

val DEFAULT_USER = User("Alice", "alice@example.com")

val DEFAULT_USERS = listOf(
DEFAULT_USER,
User("Bob", "bob@example.com"),
User("Charlie", "charlie@example.com")
)

These users are the data that the repository stores.

Let's write the repository​

The repository keeps users in memory and finds one by name:

interface UserRepository {
fun findUserOrNull(name: String): User?
fun addUsers(users: List<User>)
}

class UserRepositoryImpl : UserRepository {

private val _users = arrayListOf<User>()

override fun findUserOrNull(name: String): User? {
return _users.firstOrNull { it.name == name }
}

override fun addUsers(users: List<User>) {
_users.addAll(users)
}
}

UserRepositoryImpl has no dependency, so Koin can create it with no extra information.

Let's write the service​

UserService loads the users when it is created and prepares the greeting:

interface UserService {
fun getUserOrNull(name: String = DEFAULT_USER.name): User?
fun loadUsers()
fun prepareHelloMessage(user: User?): String
}

class UserServiceImpl(
private val userRepository: UserRepository
) : UserService {

init {
loadUsers()
}

override fun getUserOrNull(name: String): User? = userRepository.findUserOrNull(name)

override fun loadUsers() {
userRepository.addUsers(DEFAULT_USERS)
}

override fun prepareHelloMessage(user: User?): String {
return user?.let { "Hello '${user.name}' (${user.email})!" } ?: "User not found"
}
}

UserServiceImpl depends on UserRepository. Koin passes it through the constructor.

Let's declare the Koin module​

A module is where you declare the components Koin creates (see Core Concepts). Declare both components as singletons, bound to their interfaces:

import org.koin.dsl.bind
import org.koin.dsl.module
import org.koin.plugin.module.dsl.single

val appModule = module {
single<UserRepositoryImpl>() bind UserRepository::class
single<UserServiceImpl>() bind UserService::class
}

Any component that asks for UserService now gets the same UserServiceImpl instance. single and bind are described in Definitions.

Let's write the ViewModel​

The ViewModel turns a name into a greeting:

class UserViewModel(
private val userService: UserService
) : ViewModel() {

fun sayHello(name: String): String {
val user = userService.getUserOrNull(name)
val message = userService.prepareHelloMessage(user)
return "[UserViewModel] $message"
}
}

Declare it with viewModel:

import org.koin.plugin.module.dsl.viewModel

val appModule = module {
single<UserRepositoryImpl>() bind UserRepository::class
single<UserServiceImpl>() bind UserService::class
viewModel<UserViewModel>()
}

The Android ViewModel machinery now owns the instance: it survives configuration changes and is cleared with its owner. See ViewModel for parameters and SavedStateHandle.

Let's inject the ViewModel in the Activity​

Use the by viewModel() delegate in MainActivity:

import org.koin.androidx.viewmodel.ext.android.viewModel

class MainActivity : AppCompatActivity() {

private val viewModel: UserViewModel by viewModel()

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_simple)

val nameInput = findViewById<EditText>(R.id.nameInput)
val sayHelloButton = findViewById<Button>(R.id.sayHelloButton)
val textField = findViewById<TextView>(R.id.text)

sayHelloButton.setOnClickListener {
val userName = nameInput.text.toString().trim().ifEmpty { "Alice" }
textField.text = viewModel.sayHello(userName)
}
}
}

by viewModel() creates the ViewModel factory for you and binds the instance to the Activity lifecycle. The other delegates, such as activityViewModel() for Fragments, are in Android ViewModel.

Let's start Koin​

Start Koin in your Application class, and register that class in AndroidManifest.xml:

import org.koin.android.ext.koin.androidContext
import org.koin.android.ext.koin.androidLogger
import org.koin.core.context.startKoin
import org.koin.core.logger.Level

class MainApplication : Application() {

override fun onCreate() {
super.onCreate()

startKoin {
androidLogger(Level.DEBUG)
androidContext(this@MainApplication)
modules(appModule)
}
}
}

androidContext() makes the Android Context available to your definitions, and androidLogger() sends Koin logs to Logcat. Run the app, type a name and tap the button to see the greeting. See Starting Koin on Android for the other options.

Next steps​