KSP Preferences
Type-safe DataStore — generated from a Kotlin interface, zero boilerplate.
✨ Overview
KSP Preferences eliminates the DataStore boilerplate in your Kotlin project.
Annotate a plain Kotlin interface, and the KSP compiler plugin generates a fully-featured, type-safe DataStore implementation at build time — no runtime overhead.
Supports Kotlin Multiplatform (KMP) projects targeting Android, iOS, and Desktop, as well as traditional single-platform Android projects.
Your Interface ──▶ @Preferences + type annotations ──▶ KSP generates Impl
🎯 Platform Support
📦 Installation
Non-KMP Android Project
Add to your module's build.gradle.kts:
plugins {
id("com.google.devtools.ksp") version "2.3.6"
}
dependencies {
implementation("io.github.semenciuccosmin:preferences-annotations:2.0.0")
ksp("io.github.semenciuccosmin:preferences-compiler:2.0.0")
implementation("androidx.datastore:datastore-preferences:1.2.1")
}
KMP Project
Add to your shared module's build.gradle.kts:
plugins {
id("com.google.devtools.ksp") version "2.3.6"
}
kotlin {
compilerOptions {
freeCompilerArgs.add("-Xexpect-actual-classes")
}
sourceSets {
commonMain.dependencies {
implementation("io.github.semenciuccosmin:preferences-annotations:2.0.0")
implementation()
}
}
}
dependencies {
add(, )
add(, )
add(, )
add(, )
}
Note Both artifacts are published on Maven Central — no extra repository configuration needed.
🚀 Quick Start
1 — Define your preferences interface
2 — Read & write
val name = prefs.getUsername()
prefs.setUsername("Cosmin")
prefs.clear()
val name by prefs.getUsernameFlow().collectAsState(initial = "")
🏭 PreferencesFactory
PreferencesFactory provides two ways to instantiate the generated implementation, depending on your project setup.
Non-KMP Android / JVM — Reflection-based
No extra annotations or declarations needed. The factory resolves the generated *Impl class via reflection at runtime:
import io.github.semenciuccosmin.preferences.factory.PreferencesFactory
import io.github.semenciuccosmin.preferences.factory.create
val prefs: UserPreferences = PreferencesFactory.create<UserPreferences>(context)
Note This overload is available on Android and JVM only. On iOS, reflection is not supported — use the KMP approach below.
KMP — Constructor-based (Room 3 pattern)
For KMP projects, use the @ConstructedBy annotation and an expect object declaration. This avoids reflection entirely and works on all platforms including iOS.
Step 1 — Annotate your interface and declare the constructor:
import io.github.semenciuccosmin.preferences.annotations.*
import io.github.semenciuccosmin.preferences.factory.PreferencesConstructor
@Preferences(name = "user_preferences")
@ConstructedBy(UserPreferencesConstructor::class)
interface UserPreferences {
}
expect object UserPreferencesConstructor : PreferencesConstructor<UserPreferences>
Step 2 — Instantiate:
import io.github.semenciuccosmin.preferences.factory.PreferencesFactory
import io.github.semenciuccosmin.preferences.factory.create
val prefs: UserPreferences = PreferencesFactory.create(UserPreferencesConstructor, context)
val prefs: UserPreferences = UserPreferencesConstructor.initialize(context)
Note The context parameter is required on Android (pass the Context). On iOS and Desktop, pass null or omit it.
🗂 Annotation Reference
Class-level
| Annotation | Description |
|---|
@Preferences(name) | Marks an interface as a DataStore container. becomes the DataStore file name. |
Function-level — operations
Function-level — value types
Note The class passed to @ObjectPreference(clazz = ...) must be annotated with @Serializable (kotlinx.serialization), otherwise serialization will fail at runtime. Object preferences always return a nullable type — null is yielded when the key is absent.
📐 Full Interface Example
📱 Sample Apps
The repository includes two sample applications demonstrating both usage patterns:
composeApp — KMP (Android + iOS + Desktop)
A Compose Multiplatform app that uses @ConstructedBy and expect object for type-safe, reflection-free instantiation across all platforms.
@Preferences(name = PREFERENCES_NAME)
@ConstructedBy(SamplePreferencesConstructor::class)
interface SamplePreferences { }
expect object SamplePreferencesConstructor : PreferencesConstructor<SamplePreferences>
val prefs = PreferencesFactory.create(SamplePreferencesConstructor, context)
sampleAndroid — Non-KMP Android
A traditional single-platform Android app that uses the reflection-based factory. No @ConstructedBy, no expect/actual — just annotate and go.
@Preferences(name = PREFERENCES_NAME)
interface SamplePreferences { }
val prefs: SamplePreferences = PreferencesFactory.create(context)
🏛 Maven Coordinates
| Artifact | Group | Version |
|---|
preferences-annotations |
Gradle (Kotlin DSL)
implementation("io.github.semenciuccosmin:preferences-annotations:2.0.0")
ksp("io.github.semenciuccosmin:preferences-compiler:2.0.0")
Gradle (Groovy DSL)
implementation 'io.github.semenciuccosmin:preferences-annotations:2.0.0'
ksp 'io.github.semenciuccosmin:preferences-compiler:2.0.0'
Maven
<dependency>
<groupId>io.github.semenciuccosmin</groupId>
<artifactId>preferences-annotations</artifactId>
<version>2.0.0</version>
</dependency>
🔧 Requirements
| Component | Version |
|---|
| Android minSdk | 26 |
| Kotlin | 2.x |
|
📜 License
Copyright 2026 Semenciuc Cosmin
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance the License.
You may obtain a copy of the License at
https:
Unless applicable law agreed to writing, software
distributed under the License distributed an BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express implied.
See the License the specific language governing permissions
limitations under the License.