Skip to Content

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 FrameworkKMP SupportSetup
Koin✅ Đầy đủĐơn giản
Hilt❌ Android onlyPhức tạp
Dagger❌ JVM onlyPhức tạp
KodeinTrung 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

DSLMô tả
single { }Singleton
factory { }Tạo mới mỗi lần
get()Lấy dependency
named()Named dependency
parametersOf()Pass parameters

Best Practices

  1. Chia modules theo layer (data, network, viewmodel)
  2. Dùng single cho stateless dependencies
  3. Dùng factory cho ViewModels
  4. Platform modules cho platform-specific dependencies

Tiếp theo

Học về Navigation trong Compose Multiplatform.

Last updated on