Koin trong KMP
Koin là dependency injection framework đơn giản, hỗ trợ tốt Kotlin Multiplatform.
Tại sao dùng Koin cho KMP?
| DI Framework | KMP Support | Setup |
|---|---|---|
| Koin | ✅ Đầy đủ | Đơn giản |
| Hilt | ❌ Android only | Phức tạp |
| Dagger | ❌ JVM only | Phức tạp |
| Kodein | ✅ | Trung bình |
Bước 1: Thêm Dependencies
libs.versions.toml
[versions]
koin = "3.5.3"
koin-compose = "1.1.2"
[libraries]
koin-core = { module = "io.insert-koin:koin-core", version.ref = "koin" }
koin-compose = { module = "io.insert-koin:koin-compose", version.ref = "koin-compose" }
koin-android = { module = "io.insert-koin:koin-android", version.ref = "koin" }shared/build.gradle.kts
kotlin {
sourceSets {
commonMain.dependencies {
implementation(libs.koin.core)
implementation(libs.koin.compose)
}
androidMain.dependencies {
implementation(libs.koin.android)
}
}
}Bước 2: Định nghĩa Modules
commonMain/di/Modules.kt
import org.koin.core.module.Module
import org.koin.dsl.module
// Data layer module
val dataModule = module {
single { DatabaseDriverFactory() }
single { AppDatabase(get<DatabaseDriverFactory>().createDriver()) }
single { UserRepository(get()) }
single { PostRepository(get()) }
}
// Network module
val networkModule = module {
single { createHttpClient() }
single { ApiService(get()) }
}
// ViewModel module
val viewModelModule = module {
factory { UserViewModel(get()) }
factory { PostViewModel(get(), get()) }
factory { HomeViewModel(get(), get()) }
}
// Tất cả modules
val sharedModules = listOf(
dataModule,
networkModule,
viewModelModule
)Bước 3: Platform-specific Modules
androidMain/di/AndroidModules.kt
import org.koin.android.ext.koin.androidContext
import org.koin.dsl.module
val androidModule = module {
single { DatabaseDriverFactory(androidContext()) }
}iosMain/di/IosModules.kt
import org.koin.dsl.module
val iosModule = module {
single { DatabaseDriverFactory() }
}Bước 4: Khởi tạo Koin
commonMain/di/KoinInit.kt
import org.koin.core.context.startKoin
import org.koin.core.module.Module
fun initKoin(additionalModules: List<Module> = emptyList()) {
startKoin {
modules(sharedModules + additionalModules)
}
}Android - Application.kt
class MyApplication : Application() {
override fun onCreate() {
super.onCreate()
initKoin(
additionalModules = listOf(androidModule)
)
// Hoặc chi tiết hơn
startKoin {
androidContext(this@MyApplication)
modules(sharedModules + androidModule)
}
}
}iOS - AppDelegate.swift
import shared
@main
class AppDelegate: UIResponder, UIApplicationDelegate {
func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
KoinInitKt.doInitKoin(additionalModules: [])
return true
}
}Bước 5: Inject trong Compose
Cách 1: koinViewModel (Khuyến nghị)
import org.koin.compose.viewmodel.koinViewModel
@Composable
fun UserScreen(
viewModel: UserViewModel = koinViewModel()
) {
val users by viewModel.users.collectAsState()
// ...
}Cách 2: koinInject
import org.koin.compose.koinInject
@Composable
fun SettingsScreen() {
val repository: UserRepository = koinInject()
// ...
}Bước 6: Scopes
Singleton vs Factory
val myModule = module {
// Singleton - tạo một lần, dùng chung
single { HttpClient() }
// Factory - tạo mới mỗi lần inject
factory { UserViewModel(get()) }
// Scoped - singleton trong scope nhất định
scope<MainActivity> {
scoped { ScreenViewModel(get()) }
}
}Named dependencies
val networkModule = module {
single(named("baseUrl")) { "https://api.example.com" }
single(named("imageUrl")) { "https://images.example.com" }
single {
ApiService(
baseUrl = get(named("baseUrl")),
httpClient = get()
)
}
}Bước 7: ViewModel với Parameters
// Module
val viewModelModule = module {
factory { (userId: Long) ->
UserDetailViewModel(userId, get())
}
}
// Compose
@Composable
fun UserDetailScreen(userId: Long) {
val viewModel: UserDetailViewModel = koinViewModel { parametersOf(userId) }
// ...
}Ví dụ đầy đủ
Repository
class UserRepository(
private val database: AppDatabase,
private val apiService: ApiService
) {
private val queries = database.userQueries
fun observeUsers(): Flow<List<User>> {
return queries.selectAll().asFlow().mapToList(Dispatchers.IO)
}
suspend fun refreshUsers() {
val remoteUsers = apiService.getUsers()
queries.transaction {
remoteUsers.forEach { queries.insertUser(it.name, it.email, it.createdAt) }
}
}
}ViewModel
class UserViewModel(
private val repository: UserRepository
) : ViewModel() {
val users: StateFlow<List<User>> = repository.observeUsers()
.stateIn(viewModelScope, SharingStarted.Lazily, emptyList())
private val _isLoading = MutableStateFlow(false)
val isLoading: StateFlow<Boolean> = _isLoading.asStateFlow()
fun refresh() {
viewModelScope.launch {
_isLoading.value = true
try {
repository.refreshUsers()
} catch (e: Exception) {
// Handle error
} finally {
_isLoading.value = false
}
}
}
}Module hoàn chỉnh
val appModule = module {
// Database
single { DatabaseDriverFactory() }
single { AppDatabase(get<DatabaseDriverFactory>().createDriver()) }
// Network
single { createHttpClient() }
single { ApiService(get()) }
// Repositories
single { UserRepository(get(), get()) }
single { PostRepository(get(), get()) }
// ViewModels
factory { UserViewModel(get()) }
factory { PostViewModel(get()) }
factory { (userId: Long) -> UserDetailViewModel(userId, get()) }
}Screen
@Composable
fun UserListScreen(
viewModel: UserViewModel = koinViewModel(),
onUserClick: (User) -> Unit
) {
val users by viewModel.users.collectAsState()
val isLoading by viewModel.isLoading.collectAsState()
Box(modifier = Modifier.fillMaxSize()) {
LazyColumn {
items(users, key = { it.id }) { user ->
UserItem(user, onClick = { onUserClick(user) })
}
}
if (isLoading) {
CircularProgressIndicator(
modifier = Modifier.align(Alignment.Center)
)
}
}
LaunchedEffect(Unit) {
viewModel.refresh()
}
}📝 Tóm tắt
| DSL | Mô tả |
|---|---|
single { } | Singleton |
factory { } | Tạo mới mỗi lần |
get() | Lấy dependency |
named() | Named dependency |
parametersOf() | Pass parameters |
Best Practices
- Chia modules theo layer (data, network, viewmodel)
- Dùng
singlecho stateless dependencies - Dùng
factorycho ViewModels - Platform modules cho platform-specific dependencies
Tiếp theo
Last updated on