The Swiss Army Knife of Storage Solutions for Jetpack Compose Multiplatform β combining secure keychain storage, preferences, cache, and a document-style local database in one unified, type-safe library.
KDataNest is a Kotlin Multiplatform library providing a secure, fast, and flexible storage solution for Android and iOS. It combines Keychain/Encrypted storage, UserPreferences, Cache, and Local Database features with migration and versioning support, making it easy to persist and manage your app data.
- π Keychain Storage / Secure Storage β Store sensitive data securely across platforms.
- βοΈ User Preferences β Simple key-value storage for app settings.
- ποΈ Cache Storage β Temporary storage with TTL support for fast retrieval.
- ποΈ Local Database β Document-oriented storage using Kotlin Multiplatform SQLDelight.
- π Migrations β Safely upgrade database schema and transform documents.
- π‘οΈ Type-Safe Serialization β Works with Kotlinx Serialization models.
- π Search & Query Helpers β Filter documents with lambda-based queries.
Add the KDataNest library to your Kotlin Multiplatform project:
// CommonMain
dependencies {
implementation("network.chaintech:kdatanest:1.0.0")
}Securely store sensitive data such as tokens or passwords.
- π Store encrypted sensitive data
- π Platform-secure storage on Android & iOS
- π§ Persists even after uninstalls on iOS and until uninstalls on Android
- π Simple CRUD operations
- π Check existence of keys
val keychainStorage = KeychainStorageFactory.create()
// Save
keychainStorage.save("apiToken", "123456")
// Retrieve
val token = keychainStorage.get("apiToken")
// Update
keychainStorage.update("apiToken", "654321")
// Delete
keychainStorage.remove("apiToken")
// Check existence
val exists = keychainStorage.contains("apiToken")Store simple key-value pairs for app settings or user preferences.
- βοΈ Store app settings and flags
- π§ Persists until uninstalls
- ποΈ Key-value pairs with optional encryption
- π CRUD support
- π Existence checks
val preferences = PreferenceFactory.create()
// Save a preference
preferences.save("darkModeEnabled", true)
// Retrieve a preference
val darkMode = preferences.get<Boolean>("darkModeEnabled")
// Update a preference
preferences.update("darkModeEnabled", false)
// Remove a preference
preferences.remove("darkModeEnabled")
// Check if a preference exists
val hasPref = preferences.contains("darkModeEnabled")
// Clear all preference values
preferences.clearAll()Use cache storage for temporary data with expiration (TTL).
- β³ Supports TTL (time-to-live) for cache entries
- β‘ Fast retrieval of cached data
- π CRUD operations with expiration awareness
- π« Automatically removes oldest entries when limit exceeded
val cache = CacheStorageFactory.create()
// Save cache with TTL (in seconds)
cache.put("userProfile", userObj, ttl = 10.minutes)
// Retrieve cache
val cachedProfile = cache.get<UserProfile>("userProfile")
// Check if cache exists and is valid
val isCacheValid = cache.contains("userProfile")
// Remove cache entry
cache.remove("userProfile")
// Set cache count limit
cache.countLimit = 3
// Clear all cached entries
cache.clearAll()KDataNest abstracts SQLDelight into a document-oriented NoSQL database, allowing you to store and query JSON-like documents with schema migrations and versioning.
- ποΈ Collections of JSON-like documents
- π Seamlessly use Serialized models to store and retreive.
- π§ Persists until uninstalls
- π οΈ Schema migrations with versioning support
- π Query documents with lambda filters
- π CRUD operations with type-safe serialization
- π§ Migration example included
in your root level build.gradle.kts file, add this
plugins {
id("org.jetbrains.kotlin.plugin.serialization") version "2.2.20" apply false
}And in your module level build.gradle.kts file, add this
plugins {
id("org.jetbrains.kotlin.plugin.serialization") version "2.2.20"
}
sourceSets {
commonMain.dependencies {
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.9.0")
}
}
val driver = createDriver()
val dbQueries: DatabaseQueries = Database(driver).databaseQueries
val db = DocumentDatabase.create(queries = dbQueries)
// Define a data model (using kotlinx.serialization)
@Serializable
data class Note(val id: String, val title: String, val content: String)
// Save a document
val note = Note(id = "note1", title = "Shopping List", content = "Milk, Eggs, Bread")
db.save("notes", id = note.id, model = note)
// Save multiple documents
val note2 = Note(id = "note2", title = "Task List", content = "Study, Jog, Walk")
val note3 = Note(id = "note3", title = "Reading List", content = "Atomic Habits, Jane Austen, Zero to 100")
val note4 = Note(id = "note4", title = "Watch List", content = "Dexter, Hannibal, Monster")
db.saveAll("notes", listOf(note2,note3,note4))
// Retrieve a document
val savedNote = db.find("notes", "note1")
// Query documents with a filter lambda
val shoppingNotes = db.query<Note>("notes") { note ->
note.title.contains("Shop")
}
// Update a document
db.update<Note>("notes", "note1") { note ->
note.copy(content = "Milk, Eggs, Bread, Butter") // add item and update only the content
}
// Delete a document
db.delete("notes", "note1")Suppose you want to upgrade your database by adding a shiny new subtitle field to your notes π β but you donβt want any pesky null values lurking in your existing data.
Thatβs where migrations come to the rescue! π
Migrations are your databaseβs way of leveling up whenever you change the schema or need to transform existing data to support cool new features. Some common reasons to run a migration include: β’ β¨ Adding new fields with default values β’ π Renaming properties β’ π Updating data formats
Do it right, and your data stays consistent, your app keeps running smoothly, and you avoid those βoh noβ moments! π
WARNING: β It is crucial to run migrations immediately after creating or opening the database and before performing any queries or data operations. Failing to run migrations first may lead to crashes or inconsistent data states since the database schema and stored documents might not match the expected structure.
// Migration example
@Serializable
data class NewNote(val id: String, val title: String,val subTitle: String, val content: String)
db.migrateIfNeeded(targetVersion = 2) { version ->
if (version == 2) {
db.migrateCollection<Note, NewNote>("notes") { oldNote ->
NewNote(
id = oldNote.id,
title = oldNote.title,
subTitle = "",
content = oldNote.content
)
}
}
}// Delete a single document
db.delete("notes", "note1")
// Batch delete multiple documents
db.deleteAll("notes", listOf("note2", "note3"))
// Clear a whole collection
db.clearCollection("tasks")
// Count documents in a collection
val noteCount = db.countDocuments("notes")
// Check if a document exists
val exists = db.documentExists("users", "user1")
// Get all document IDs in a collection
val ids = db.getAllDocumentIds("tasks")
// Check if a collection is empty
val isEmpty = db.isCollectionEmpty("logs")
// Get all collections in the database
val collections = db.getAllCollections()
// Get all documents as a list of models
val allUsers = db.getAllDocuments<User>("users")
// Update a document
db.update<User>("users", "user1") { user ->
user.copy(name = "Alice Cooper")
}
// Schema versioning and migration
db.setSchemaVersion(1)
val version = db.getSchemaVersion()
db.migrateIfNeeded(targetVersion = 2) { version ->
if (version == 2) {
db.migrateCollection<User, User>("users") { oldUser ->
oldUser.copy(email = "${oldUser.name.lowercase()}@example.com")
}
}
}KDataNest leverages Kotlin Multiplatform SQLDelight but exposes a document-oriented API to developers. Instead of dealing with raw SQL tables and queries, you work with collections of JSON-like documents identified by unique keys.
- Collections: Group documents by collection names (e.g.,
"notes","users"). - Documents: Store any serializable Kotlin object as a document.
- Queries: Filter documents with lambda expressions, enabling flexible search.
- Migrations: Seamlessly upgrade schemas and transform documents without losing data.
- Versioning: Track document versions to maintain data integrity.
This abstraction allows you to enjoy the power and reliability of SQL databases with the flexibility and simplicity of NoSQL document stores.
More features and enhancements for the database are coming soon β KDataNest is only getting started. π
| Feature | Keychain Storage π | Preferences Storage βοΈ | Cache Storage ποΈ | Local Database ποΈ |
|---|---|---|---|---|
| Purpose | Encrypt and Store sensitive data securely | Store app settings and preferences | Temporary data with expiration (TTL) | Persistent document-oriented storage |
| Data Model | Key-value pairs | Key-value pairs | Key-value pairs with TTL | Serialized models |
| TTL Support | β | β | β | β |
| Query Support | Limited (by key) | Limited (by key) | Limited (by key) | Advanced query with lambda filters |
| Migration Support | β | β | β | β |
| Use Cases | Tokens, passwords, secrets | User preferences, flags | Cached API responses, temp data | Complex serialized model data, offline storage, sync |
| Performance | Fast, secure | Fast | Very fast | Moderate (depends on queries) |
KDataNest simplifies cross-platform data persistence with a unified, type-safe API. Whether you need secure storage, preferences, caching, or a full-fledged document database, KDataNest has you covered.
For more details, visit the GitHub repository and check out the examples.
Happy coding! π
Chaintech Network
Stay connected and keep up with our latest innovations! πΌ Let's innovate together!
If you find this library useful:
β Star the repo β it helps others discover it!
π¬ Open an issue or PR if youβd like to contribute.
Copyright 2025 Mobile Innovation Network
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

