Step 13 of 15
Koin setup for Android, module definitions, viewModel scope, initialization
Dependency Injection ด้วย Koin — module definitions, viewModel scope
Koin is a lightweight dependency injection framework for Kotlin. It has no code generation and no annotations — you declare dependencies in plain Kotlin modules. Current version: 4.0.0.
| Feature | Koin | Hilt |
|---|---|---|
| Setup | Plain Kotlin modules | Annotations + kapt/ksp |
| Compile speed | Fast (no generation) | Slower (annotation processing) |
| Learning curve | Low | Higher |
| Multiplatform | Yes | No |
| Runtime errors | At runtime | At compile time |
Choose Koin for speed and Multiplatform support. Choose Hilt if you want compile-time safety.
// build.gradle.kts
dependencies {
implementation("io.insert-koin:koin-android:4.0.0")
implementation("io.insert-koin:koin-androidx-compose:4.0.0")
}
A module is a set of definitions. Use single for one instance shared across the app, and factory for a new instance every time.
import org.koin.dsl.module
val appModule = module {
// one HttpClient shared by the whole app
single { createHttpClient() }
// repository depends on the HttpClient
single<TaskRepository> { KtorTaskRepository(get()) }
// a new ApiService each time (stateless helper)
factory { DateHelper() }
}
single { } — one instance, reusedfactory { } — new instance each timeget() — resolve another dependency already declared in the moduleUse viewModelOf to declare a ViewModel. Koin manages its lifecycle with the Activity/Composable.
val appModule = module {
single<TaskRepository> { RoomTaskRepository(get()) }
viewModelOf(::TaskViewModel)
}
Koin calls TaskViewModel(get()), injecting the repository automatically.
Start Koin in your Application class:
import android.app.Application
import org.koin.android.ext.koin.androidContext
import org.koin.core.context.startKoin
class MyApp : Application() {
override fun onCreate() {
super.onCreate()
startKoin {
androidContext(this@MyApp)
modules(appModule, databaseModule)
}
}
}
Register the Application class in the manifest:
<application
android:name=".MyApp"
android:label="MyApp"
android:theme="@style/Theme.MyApp">
<!-- activities -->
</application>
Use koinViewModel() to get a ViewModel scoped to the Composable, and koinInject() for other dependencies.
import org.koin.androidx.compose.koinViewModel
@Composable
fun TaskScreen(viewModel: TaskViewModel = koinViewModel()) {
val uiState by viewModel.uiState.collectAsStateWithLifecycle()
// ... render UI ...
}
Keep modules small and focused. Here is a module just for Room:
val databaseModule = module {
single { AppDatabase.getInstance(androidContext()) }
single { get<AppDatabase>().taskDao() }
}
val appModule = module {
single<TaskRepository> { RoomTaskRepository(get()) }
single<PostRepository> { KtorPostRepository(get()) }
viewModelOf(::TaskViewModel)
viewModelOf(::PostViewModel)
}
In tests, replace the real dependencies with fakes using a test module:
import org.koin.test.KoinTest
import org.koin.dsl.koinApplication
import org.koin.test.inject
class TaskViewModelTest : KoinTest {
private val viewModel: TaskViewModel by inject()
@BeforeTest
fun setup() {
startKoin {
modules(module {
single<TaskRepository> { FakeTaskRepository() }
viewModelOf(::TaskViewModel)
})
}
}
@AfterTest
fun teardown() {
stopKoin()
}
}
Create two Koin modules: networkModule (HttpClient, repositories) and databaseModule (AppDatabase, DAOs). Add a SettingsViewModel that takes a SettingsRepository. Inject it into a Compose screen with koinViewModel(). Confirm the app starts and the ViewModel receives its dependencies.