KMP项目中一般使用SharedPreferences(Android)、NSUserDefaults(iOS)来存储键值对。
kotlin
// Android端 - SharedPreferences的"简洁"实现
val prefs = context.getSharedPreferences("my_prefs", Context.MODE_PRIVATE)
val darkMode = prefs.getBoolean("dark_mode_flag", false)
prefs.edit().putBoolean("dark_mode_flag", true).apply() // 用commit还是apply?这是个问题
// iOS端 - NSUserDefaults的Kotlin版
val defaults = NSUserDefaults.standardUserDefaults
val darkMode = defaults.boolForKey("dark_mode_flag")
defaults.setBool(true, forKey = "dark_mode_flag")
// 再加上expect/actual的地狱级样板:
expect class PreferenceManager {
fun saveString(key: String, value: String)
fun getString(key: String): String?
// 每种类型重复一遍,直到天荒地老
}
这种代码的痛点,每个跨平台开发者都深有体会:
- 🚫 魔法字符串 满天飞
- 🚫 平台差异 需要处理平台差异
- 🚫 管理混乱 键值对管理没有统一管理
由此,我们引入了AppConfig,希望打造优雅的键值对管理,理念是配置管理应该像定义接口一样简单
看看AppConfig如何实现:
kotlin
// 1. 定义接口
@Config
interface NetworkConfig {
@IntProperty(
defaultValue = 60,
description = "Network timeout duration"
)
var networkTimeout: Int
}
// 2. 获取配置实例
val prefs = AppConfig.networkconfig
// 3. 读写配置
prefs.networkTimeout = 45
println("${prefs.networkTimeout}")
三步搞定:
- 定义接口
- 获取实例
- 像普通属性一样读写
没有魔法字符串,没有类型转换,没有平台判断------只有纯粹的Kotlin。
实现机制
AppConfig 的架构由两个核心部分组成:
1. KSP 代码生成器
Kotlin Symbol Processing (KSP) 在编译时分析你的配置接口并生成所有样板代码:
css
┌─────────────────┐ KSP ┌──────────────────┐
│ @Config │ ────────→ │ 生成的 │
│ 接口定义 │ 处理器 │ 实现类 │
└─────────────────┘ └──────────────────┘
│
▼
┌──────────────────┐
│ 扩展属性 │
│ AppConfig.xxx │
└──────────────────┘
2. appconfig-lib 跨平台存储
appconfig-lib模块 提供了统一的跨平台键值对存储机制:
scss
┌──────────────────┐ ┌──────────────────┐
│ 生成的实现类 │ ────────→ │ ConfigStore │
│ (类型安全接口) │ │ (统一抽象层) │
└──────────────────┘ └──────────────────┘
│
┌───────────────────┼───────────────────┐
▼ ▼ ▼
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ Android │ │ iOS │ │ JVM/Desktop │
│SharedPreferences│ │ NSUserDefaults │ │ Properties │
└─────────────────┘ └─────────────────┘ └─────────────────┘
完整工作流程:
- 编译时 :KSP 处理器扫描
@Config
注解的接口 - 代码生成:为每个配置接口生成实现类和扩展属性
- 运行时 :生成的代码通过
ConfigStore
抽象层访问平台存储 - 平台适配 :
ConfigStore
自动选择合适的底层存储机制
生成的内容包括:
- ✅ 带有类型安全 getter/setter 的实现类
- ✅ 便于访问的扩展属性(
AppConfig.networkconfig
) - ✅ 与 ConfigStore 的集成代码
- ✅ 管理界面的配置元数据
- ✅ 批量操作(重置、远程更新等)
appconfig-lib 提供的核心功能:
- 🔄 跨平台键值对存储抽象
- 🛡️ 类型安全的数据转换
- ⚡ 高性能的缓存机制
- 🔧 统一的配置管理 API
我们可以以上面的NetworkConfig接口为例,看编译生成的代码:
kotlin
public class NetworkConfigImpl : NetworkConfig {
private val store: ConfigStore = AppConfig.getConfigStore("NetworkConfig")
private var networkTimeoutDefault: Int = 60
override var networkTimeout: Int
get() = store.getInt("networkTimeout", networkTimeoutDefault)
set(`value`) {
store.putInt("networkTimeout", value)
}
public fun getConfigItems(): List<ConfigItemDescriptor<*>> = listOf(
StandardConfigItem<Int>(
key = "networkTimeout",
groupName = "NetworkConfig",
description = "Network timeout duration",
getCurrentValue = { this.networkTimeout },
defaultValue = 60,
panelType = PanelType.TEXT_INPUT,
dataType = DataType.INT,
updateValue = { newValue -> this.networkTimeout = newValue },
resetToDefault = { this.networkTimeout = 60 }
))
}
public val AppConfig.networkconfig: NetworkConfigImpl
get() = NetworkConfigImpl()
可以看到,其中包括:
- ✅ 带有类型安全 getter/setter 的实现类UserInterfaceConfigImpl
- ✅ 便于访问的扩展属性(
AppConfig.networkconfig
) - ✅ 获取ConfigStore,处理键值对的存储
- ✅ 管理界面的配置元数据,用于可视化管理所有键值对
平台特定配置
AppConfig不仅仅生成平台无关的代码 - 它实际上支持平台特定配置,这些配置只存在于特定平台上
共享配置(commonMain)
适用于所有平台,在 commonMain
中定义:
kotlin
// commonMain/kotlin/.../UserSettings.kt
@Config(groupName = "UserSettings")
interface UserSettings {
@BooleanProperty(defaultValue = false, description = "启用深色模式")
var isDarkModeEnabled: Boolean
@IntProperty(defaultValue = 30, description = "请求超时时间(秒)")
var timeoutSeconds: Int
}
AppConfig 在 commonMain
中生成实现,你可以在任何地方使用:
kotlin
// 在 Android 和 iOS 上都能工作
val settings = AppConfig.usersettings
settings.isDarkModeEnabled = true
Android 专用配置(androidMain)
可以定义只在 Android 上存在的设置:
kotlin
// androidMain/kotlin/.../AndroidSettings.kt
@Config
interface AndroidSettings {
@BooleanProperty(defaultValue = false, description = "启用 Camera X")
var isCameraXEnabled: Boolean
@BooleanProperty(defaultValue = true, description = "使用 Material You 颜色")
var useMaterialYou: Boolean
@IntProperty(defaultValue = 2, description = "通知重要性级别")
var notificationImportance: Int
}
AppConfig 检测到这是在 androidMain
中,只为 Android 生成代码:
kotlin
// 只在 Android 目标平台生成
class AndroidSettingsImpl : AndroidSettings {
private val store = AppConfig.getConfigStore("AndroidSettings")
override var isCameraXEnabled: Boolean
get() = store.getBoolean("isCameraXEnabled", false)
set(value) = store.putBoolean("isCameraXEnabled", value)
override var useMaterialYou: Boolean
get() = store.getBoolean("useMaterialYou", true)
set(value) = store.putBoolean("useMaterialYou", value)
// ... 等等
}
// 扩展属性只在 Android 上可用
val AppConfig.androidsettings: AndroidSettingsImpl
get() = AndroidSettingsImpl()
iOS中也可以定义类似于Android的专属配置
支持的数据类型
Annotation | Kotlin Type | Description |
---|---|---|
@StringProperty |
String |
Text values |
@BooleanProperty |
Boolean |
True/false toggles |
@IntProperty |
Int |
32-bit integers |
@LongProperty |
Long |
64-bit integers |
@FloatProperty |
Float |
32-bit floating point |
@DoubleProperty |
Double |
64-bit floating point |
@OptionProperty |
Sealed Class |
Enum-like choices |
支持密封类、密封接口
使用方式如下:
kotlin
@Config
interface AppSettings {
@OptionProperty(description = "应用主题")
var theme: AppTheme
}
@Option
sealed class AppTheme {
@OptionItem(optionId = 0, description = "跟随系统", isDefault = true)
object System : AppTheme()
@OptionItem(optionId = 1, description = "浅色主题")
object Light : AppTheme()
@OptionItem(optionId = 2, description = "深色主题")
object Dark : AppTheme()
}
// 使用方式类型安全且优雅
settings.theme = AppTheme.Dark
when (settings.theme) {
AppTheme.System -> applySystemTheme()
AppTheme.Light -> applyLightTheme()
AppTheme.Dark -> applyDarkTheme()
}
生成的代码内部将这些存储为整数,但是使用的时候可以保证完整类型安全。
适合A/B 测试: 这个选项系统对于功能开关和 A/B 测试组来说非常强大。不用管理多个布尔标志或神秘的字符串标识符,都可以定义清晰、类型安全的变体:
kotlin
@Option
sealed class OnboardingGroup {
@OptionItem(0, "A 组 - 教程", isDefault = true)
object Tutorial : OnboardingGroup()
@OptionItem(1, "B 组 - 交互演示")
object Demo : OnboardingGroup()
@OptionItem(2, "C 组 - 跳过引导")
object Skip : OnboardingGroup()
}
自动生成的管理界面
kotlin
@Composable
fun DebugSettingsScreen() {
ConfigPanel(
configItems = AppConfig.getAllConfigItems()
)
}
这会生成一个完整的界面,包含开关、文本框、下拉菜单和所有好东西。就像每个配置接口都免费送一个设置界面。
适合A/B 测试: 这个功能对于需要 A/B 测试的应用特别强大。不用在你的 A/B 测试平台上切换测试组,你可以在应用内即时更改测试配置。QA 团队可以即时测试不同测试组,开发者可以快速验证功能开关,产品经理可以准确看到不同配置如何影响用户体验 - 所有这些都不用离开应用或等待远程配置更新。
快速上手
🚀 开始使用: AppConfig on GitHub