You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何基于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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.15 21:54:54