This is a pre-release version. It is not yet ready for general use and is currently being tested in internal projects.
Use it in your own projects at your own risk.
A lightweight Kotlin Multiplatform caching library designed to work using a key-based strategy system and flexible caching policies.
- Simple key-based cache access using typed keys
- Customizable caching strategies
- Supports suspending and flow-based data operations
- Pluggable storage backend (
Store) for full platform flexibility - Built-in logging interface
- Fully testable and decoupled from Android dependencies
Add the library to your Kotlin Multiplatform project (available soon via Maven Central / GitHub Packages).
Gradle (Kotlin DSL)
dependencies {
implementation("com.dapadz:cachedflow:<version>")
}The Store interface abstracts the storage layer:
interface Store {
suspend fun <T: Any> get(key: StoreKey<T>): Flow<T?>
suspend fun <T: Any> save(key: StoreKey<T>, value: T)
suspend fun <T: Any> delete(key: StoreKey<T>)
suspend fun clear()
}Implement it using your platform’s local storage (e.g. DataStore, SharedPreferences, NSUserDefaults, file system, etc).
fun main() {
val store: Store = MyMultiplatformStore()
Cache.initialize(store)
}Optionally, provide a custom Logger:
Cache.initialize(store, logger = MyLogger())Use built-in key helpers or define your own:
val userKey = stringCacheKey("user_profile")
val ageKey = integerCacheKey("user_age")flow { emit(fetchUserProfileFromApi()) }
.cache(userKey, CacheStrategyType.IF_HAVE)
.collect { user -> println("User: $user") }Choose how the cache behaves during a Flow emission:
| Strategy | Description |
|---|---|
ONLY_CACHE |
Always use the cache. Throws if no value exists. |
ONLY_REQUEST |
Always skip cache. Fetch fresh and optionally cache it. |
IF_HAVE (default) |
Use cache if available, otherwise fallback to the flow. |
Implement the CacheStrategy<T> interface for full control:
abstract class CacheStrategy <T> (
protected val key: Key<T>,
protected val cachedAfterLoad : Boolean
) {
abstract suspend fun execute(currentFlow: Flow<T>): Flow<T>
}Use the following factory functions to quickly define typed keys for common primitive types:
| Key Type | Factory Function | Example |
|---|---|---|
String |
stringCacheKey(name) |
val key = stringCacheKey("username") |
Int |
integerCacheKey(name) |
val key = integerCacheKey("user_age") |
Float |
floatCacheKey(name) |
val key = floatCacheKey("user_score") |
Boolean |
booleanCacheKey(name) |
val key = booleanCacheKey("is_logged") |
These keys inherit from Key<T> and include built-in logic to handle type-safe caching operations.
You can also define your own cache key for complex or custom types:
class MyKey(name: String): Key<MyType>(name) {
override fun isTypeOf(valueClass: KClass<*>) = valueClass == MyType::class
override suspend fun getFromStore(store: Store): Flow<MyType?> = ...
override suspend fun saveToStore(item: MyType, store: Store) = ...
}To simplify integration and expand functionality, additional extension modules are available. They allow you to quickly connect the library for specific platforms and use cases.
A set of extensions for convenient work on Android. The module includes:
- a ready-to-use
Storeimplementation based onSharedPreferences AndroidLogger, which uses the standardLog
dependencies {
implementation("com.dapadz:cachedflow:<version>")
implementation("com.dapadz:cachedflow-ext-android:<version>")
}Example of initializing Cache with SharedPreferenceStore and AndroidLogger:
private fun initializeCache() {
Cache.initialize(
store = SharedPreferenceStore(context = this),
logger = AndroidLogger()
)
}An extension that adds support for Kotlinx Serialization.
It allows storing and restoring Serializable classes from the cache.
Includes convenient keys:
serializableKey— for a single objectserializableListKey— for a list of objects
dependencies {
implementation("com.dapadz:cachedflow:<version>")
implementation("com.dapadz:cachedflow-ext-serialization:<version>")
}Example of caching a Serializable class:
@Serializable
data class Dog(val name: String)
fun getGoodDog(): Flow<Dog> {
return dogRepository.getGoodDog()
.cache(serializableKey("goodDog"))
}You can also use SerializersModule for more complex scenarios —
for example, serializing interfaces and polymorphic classes:
interface Animal {
val name: String
}
@Serializable
data class Dog(
override val name: String,
val isGoodBoy: Boolean
) : Animal
@Serializable
data class Cat(
override val name: String
) : Animal
fun getAnimals(): Flow<List<Animal>> {
return repository.getAnimals()
.cache(
serializableListKey(
name = "animals",
module = SerializersModule {
polymorphic(Animal::class) {
subclass(Cat::class)
subclass(Dog::class)
}
}
)
)
}