如何基于KMP构建主题偏好存储库?解决expect/actual适配难题
KMP主题持久化库构建指南:解决expect/actual与平台适配问题
一、核心问题修复:Expect/Actual的正确写法
你的核心困境是平台实现的构造参数差异(Android需要Context,iOS无依赖),无法直接用无参工厂函数统一创建实例。以下是标准解决方案:
1. Common层定义统一接口与适配型工厂函数
在commonMain中,我们定义带可选参数的工厂函数,适配不同平台的依赖需求:
// commonMain enum class AppThemeScheme { LIGHT, DARK, SYSTEM } interface ThemePreferences { suspend fun saveTheme(appThemeScheme: AppThemeScheme) fun observeTheme(): Flow<AppThemeScheme> } // 可选参数适配Android/iOS的差异 expect fun createThemePreferences(context: Context? = null): ThemePreferences
2. Android平台实现
在androidMain中,强制校验Context参数,基于DataStore实现逻辑:
// androidMain import androidx.datastore.core.DataStore import androidx.datastore.preferences.core.Preferences import androidx.datastore.preferences.core.edit import androidx.datastore.preferences.core.stringPreferencesKey import androidx.datastore.preferences.preferencesDataStore import kotlinx.coroutines.flow.map actual fun createThemePreferences(context: Context?): ThemePreferences { requireNotNull(context) { "Context must be provided for Android platform" } return ThemePreferencesImpl(context) } private val Context.dataStore by preferencesDataStore(name = "theme_preferences") class ThemePreferencesImpl(private val context: Context) : ThemePreferences { override suspend fun saveTheme(appThemeScheme: AppThemeScheme) { context.dataStore.edit { preferences -> preferences[THEME_MODE_KEY] = appThemeScheme.name } } override fun observeTheme() = context.dataStore.data.map { preferences -> when (preferences[THEME_MODE_KEY]) { AppThemeScheme.LIGHT.name -> AppThemeScheme.LIGHT AppThemeScheme.DARK.name -> AppThemeScheme.DARK else -> AppThemeScheme.SYSTEM } } companion object { private val THEME_MODE_KEY = stringPreferencesKey("app_theme_scheme") } }
3. iOS平台实现
在iosMain中,忽略Context参数,基于NSUserDefaults实现,并补全Flow监听逻辑(iOS无原生DataStore,需用CallbackFlow包装KVO监听):
// iosMain import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.flow.callbackFlow import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.Dispatchers import platform.Foundation.NSKeyValueObservation import platform.Foundation.NSKeyValueObservingOptionsNew import platform.Foundation.NSUserDefaults actual fun createThemePreferences(context: Context?): ThemePreferences { return ThemePreferencesImpl() } class ThemePreferencesImpl : ThemePreferences { private val userDefaults = NSUserDefaults.standardUserDefaults override suspend fun saveTheme(theme: AppThemeScheme) { userDefaults.setObject(theme.name, forKey = "theme") userDefaults.synchronize() } override fun observeTheme() = callbackFlow { // 监听UserDefaults的theme键变化 val observer = NSKeyValueObservation.observe( NSUserDefaults.standardUserDefaults, keyPath = "theme", options = NSKeyValueObservingOptionsNew ) { _, _ -> trySend(getCurrentTheme()) } // 发送初始值 trySend(getCurrentTheme()) // 关闭协程时销毁监听 awaitClose { observer.invalidate() } }.flowOn(Dispatchers.Main) // 适配iOS线程模型,切换到主协程 private fun getCurrentTheme(): AppThemeScheme { val themeValue = userDefaults.stringForKey("theme") ?: AppThemeScheme.SYSTEM.name return AppThemeScheme.valueOf(themeValue) } }
二、库的基础配置要点
1. 依赖配置(build.gradle.kts)
确保多平台模块正确引入协程与平台依赖:
plugins { kotlin("multiplatform") id("com.android.library") } kotlin { androidTarget() iosX64() iosArm64() iosSimulatorArm64() sourceSets { val commonMain by getting { dependencies { implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3") } } val androidMain by getting { dependencies { implementation("androidx.datastore:datastore-preferences:1.0.0") } } val iosMain by getting { dependencies { implementation("org.jetbrains.kotlinx:kotlinx-coroutines-ios:1.7.3") } } } }
2. 平台使用示例
- Android侧:
val themePrefs = createThemePreferences(applicationContext) // 保存主题 lifecycleScope.launch { themePrefs.saveTheme(AppThemeScheme.DARK) } // 监听主题变化 themePrefs.observeTheme().collect { theme -> // 应用主题逻辑 } - iOS侧(Swift):
let themePrefs = createThemePreferences(context: nil) // 保存主题 Task { try await themePrefs.saveTheme(.dark) } // 监听主题变化 themePrefs.observeTheme().sink(receiveValue: { theme in // 应用主题逻辑 })
三、关键适配说明
- 参数兼容性:通过可选参数
context: Context? = null统一工厂函数签名,Android侧强制校验非空,iOS侧忽略参数。 - Flow跨平台适配:iOS无原生DataStore的Flow支持,用
CallbackFlow包装NSUserDefaults的KVO监听,确保流的行为与Android一致。 - 协程处理:KMP自动适配suspend函数在iOS的执行逻辑,只需确保引入
kotlinx-coroutines-ios依赖。
内容的提问来源于stack exchange,提问作者noloman
相关产品推荐
相关产品推荐

