Skip to content

依赖注入:Koin

源:Koin Documentation

Koin 是 Kotlin Multiplatform 中流行的轻量级依赖注入框架。它使用纯 Kotlin DSL,无需注解处理器,非常适合跨平台项目。

基础配置

依赖配置

toml
[versions]
koin = "4.0.1"

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

# Android 专用
koin-android = { module = "io.insert-koin:koin-android", version.ref = "koin" }
koin-androidx-compose = { module = "io.insert-koin:koin-androidx-compose", version.ref = "koin" }
kotlin
kotlin {
    sourceSets {
        commonMain.dependencies {
            implementation(libs.koin.core)
        }
        
        commonTest.dependencies {
            implementation(libs.koin.test)
        }
        
        androidMain.dependencies {
            implementation(libs.koin.android)
            implementation(libs.koin.androidx.compose)
        }
    }
}

最新版本查询:https://github.com/InsertKoinIO/koin/releases

模块定义

kotlin
// commonMain
import org.koin.core.module.Module
import org.koin.dsl.module

// 定义共享模块
val dataModule = module {
    // 单例:整个应用生命周期内只有一个实例
    single<UserRepository> { 
        UserRepositoryImpl(get(), get()) 
    }
    
    // 工厂:每次调用 get() 都创建新实例
    factory { 
        UserValidator() 
    }
    
    // 带命名的依赖
    single(named("apiKey")) { "YOUR_API_KEY" }
}

val networkModule = module {
    single { 
        createHttpClient() 
    }
    
    single<RemoteDataSource> { 
        RemoteDataSourceImpl(get()) 
    }
}

val domainModule = module {
    factory { 
        GetUserUseCase(get()) 
    }
    
    factory { 
        LoginUseCase(get(), get()) 
    }
}

// 组合所有共享模块
val sharedModules = listOf(
    dataModule,
    networkModule,
    domainModule
)

平台特定模块

kotlin
import org.koin.dsl.module
import android.content.Context

val androidModule = module {
    // Android Context
    single { 
        get<Context>().getSharedPreferences("app_prefs", Context.MODE_PRIVATE) 
    }
    
    single<LocalDataSource> { 
        AndroidLocalDataSource(get()) 
    }
    
    single<Logger> { 
        AndroidLogger() 
    }
}
kotlin
import org.koin.dsl.module

val iosModule = module {
    single<LocalDataSource> { 
        IosLocalDataSource() 
    }
    
    single<Logger> { 
        IosLogger() 
    }
}

初始化 Koin

平台特定初始化

kotlin
import org.koin.core.context.startKoin
import org.koin.core.module.Module

fun initKoin(platformModules: List<Module> = emptyList()) {
    startKoin {
        modules(sharedModules + platformModules)
    }
}
kotlin
import android.app.Application
import org.koin.android.ext.koin.androidContext
import org.koin.android.ext.koin.androidLogger

class MyApplication : Application() {
    override fun onCreate() {
        super.onCreate()
        
        startKoin {
            // Android 日志
            androidLogger()
            // 注入 Android Context
            androidContext(this@MyApplication)
            // 加载模块
            modules(sharedModules + androidModule)
        }
    }
}
kotlin
fun initKoinIos() {
    initKoin(listOf(iosModule))
}

// 在 Swift 中调用
// KoinKt.initKoinIos()

依赖注入

构造函数注入

kotlin
// 自动解析依赖
class UserRepository(
    private val api: ApiService,      // Koin 自动注入
    private val database: Database    // Koin 自动注入
) {
    suspend fun getUser(id: String): User {
        return api.fetchUser(id)
    }
}

// 在模块中定义
val appModule = module {
    single { ApiService(get()) }
    single { Database(get()) }
    single { UserRepository(get(), get()) }  // 自动解析参数
}

属性注入

kotlin
import org.koin.core.component.KoinComponent
import org.koin.core.component.inject

class UserViewModel : KoinComponent {
    // 懒加载注入
    private val repository: UserRepository by inject()
    private val logger: Logger by inject()
    
    // 带命名的注入
    private val apiKey: String by inject(named("apiKey"))
    
    fun loadUser(id: String) {
        logger.log("Loading user: $id")
        // 使用 repository
    }
}

直接获取

kotlin
import org.koin.core.component.KoinComponent
import org.koin.core.component.get

class MyClass : KoinComponent {
    fun doSomething() {
        // 直接获取实例
        val repository: UserRepository = get()
        
        // 带命名参数
        val apiKey: String = get(named("apiKey"))
    }
}

Scope 作用域

定义 Scope

kotlin
val appModule = module {
    // 定义 Scope
    scope<UserActivity> {
        scoped { UserPresenter(get()) }
        scoped { UserAdapter() }
    }
    
    // 或使用命名 Scope
    scope(named("user_session")) {
        scoped { SessionManager() }
        scoped { AuthToken() }
    }
}

使用 Scope

kotlin
class UserActivity : AppCompatActivity(), KoinScopeComponent {
    override val scope: Scope by activityScope()
    
    // 从 Scope 中获取
    private val presenter: UserPresenter by inject()
    
    override fun onDestroy() {
        super.onDestroy()
        // Scope 结束时自动清理
        scope.close()
    }
}

// 或手动管理
class MyViewModel : KoinComponent {
    private val sessionScope = getKoin().createScope("session_id", named("user_session"))
    
    private val sessionManager: SessionManager = sessionScope.get()
    
    fun cleanup() {
        sessionScope.close()
    }
}

ViewModel 集成

Android ViewModel

kotlin
// androidMain
import androidx.lifecycle.ViewModel
import org.koin.androidx.viewmodel.dsl.viewModel

val viewModelModule = module {
    viewModel { UserViewModel(get()) }
    viewModel { (userId: String) -> UserDetailViewModel(userId, get()) }
}

class UserViewModel(
    private val repository: UserRepository
) : ViewModel() {
    // ViewModel 逻辑
}

// 在 Activity/Fragment 中使用
class UserFragment : Fragment() {
    private val viewModel: UserViewModel by viewModel()
    
    // 带参数的 ViewModel
    private val detailViewModel: UserDetailViewModel by viewModel {
        parametersOf("user_123")
    }
}

// 在 Compose 中使用
@Composable
fun UserScreen() {
    val viewModel: UserViewModel = koinViewModel()
    
    // 使用 viewModel
}

共享 ViewModel 逻辑

kotlin
// commonMain - 共享逻辑
class UserViewModel(private val repository: UserRepository) {
    private val _users = MutableStateFlow<List<User>>(emptyList())
    val users: StateFlow<List<User>> = _users.asStateFlow()
    
    fun loadUsers() {
        viewModelScope.launch {
            _users.value = repository.getUsers()
        }
    }
}

// androidMain - ViewModel 包装
class AndroidUserViewModel(
    repository: UserRepository
) : ViewModel() {
    private val sharedViewModel = UserViewModel(repository)
    
    val users = sharedViewModel.users
    
    fun loadUsers() = sharedViewModel.loadUsers()
}

参数化注入

传递参数

kotlin
val userModule = module {
    factory { (userId: String) ->
        UserDetailPresenter(userId, get())
    }
    
    factory { (id: String, name: String) ->
        User(id, name)
    }
}

// 使用
class MyClass : KoinComponent {
    fun loadUserDetail(userId: String) {
        val presenter: UserDetailPresenter = get { parametersOf(userId) }
    }
    
    // 多参数
    val user: User = get { parametersOf("123", "Alice") }
}

测试支持

单元测试

kotlin
import org.koin.test.KoinTest
import org.koin.test.inject
import org.koin.test.get
import kotlin.test.Test
import kotlin.test.assertEquals

class UserRepositoryTest : KoinTest {
    private val repository: UserRepository by inject()
    
    @Before
    fun setup() {
        startKoin {
            modules(testModule)
        }
    }
    
    @After
    fun teardown() {
        stopKoin()
    }
    
    @Test
    fun testGetUser() = runTest {
        val user = repository.getUser("123")
        assertEquals("Alice", user.name)
    }
}

val testModule = module {
    single<ApiService> { MockApiService() }
    single { UserRepository(get()) }
}

Mock 依赖

kotlin
import org.koin.test.mock.declareMock

class MyTest : KoinTest {
    @Test
    fun testWithMock() {
        // 声明 Mock
        declareMock<ApiService> {
            coEvery { fetchUser(any()) } returns User("123", "Mock User")
        }
        
        val repository: UserRepository = get()
        // 测试使用 mock 的 repository
    }
}

最佳实践

✅ 实践 1:模块化组织

kotlin
// ✅ 按功能划分模块
val authModule = module { /* 认证相关 */ }
val userModule = module { /* 用户相关 */ }
val postModule = module { /* 帖子相关 */ }

// ❌ 避免将所有依赖放在一个模块
val hugeModule = module {
    // 100+ 个依赖定义...
}

✅ 实践 2:使用接口而非具体类

kotlin
// ✅ 推荐
interface UserRepository {
    suspend fun getUser(id: String): User
}

val dataModule = module {
    single<UserRepository> { UserRepositoryImpl(get()) }
}

// ❌ 不推荐
val dataModule = module {
    single { UserRepositoryImpl(get()) }  // 耦合具体实现
}

✅ 实践 3:避免循环依赖

kotlin
// ❌ 循环依赖
class A(val b: B)
class B(val a: A)

// ✅ 重构消除循环
class A(val service: Service)
class B(val service: Service)
class Service()

✅ 实践 4:使用命名依赖区分相似类型

kotlin
val configModule = module {
    single(named("apiUrl")) { "https://api.example.com" }
    single(named("cdnUrl")) { "https://cdn.example.com" }
}

class ApiClient : KoinComponent {
    private val apiUrl: String by inject(named("apiUrl"))
}

Koin vs 其他 DI 方案

特性KoinDagger/HiltKodein
KMP 支持✅ 完整❌ 仅 Android✅ 完整
运行时检查❌ 编译时
DSL 风格✅ 简洁❌ 注解✅ 灵活
性能良好优秀良好
学习曲线
ViewModel✅ 原生支持⚠️ 需扩展

常见问题

问题 1:KoinApplication has already been started

kotlin
// ❌ 多次初始化
startKoin { }
startKoin { }  // 错误

// ✅ 检查是否已初始化
if (getKoinApplicationOrNull() == null) {
    startKoin { modules(appModule) }
}

问题 2:No definition found

kotlin
// 确保依赖已定义
val myModule = module {
    single { MyService() }  // 必须定义
}

startKoin {
    modules(myModule)  // 必须加载模块
}

Koin 以其简洁的 DSL 和对 Kotlin Multiplatform 的良好支持,成为跨平台项目中依赖注入的首选方案。