Kache
Cache Library
Kache
Overview
Kache is a lightweight Kotlin Multiplatform caching library that supports both in-memory and file-based persistent caches, offering multiple eviction strategies (LRU, FIFO, MRU, and FILO).
Details
Kache is a Kotlin Multiplatform caching library developed by MayakaApps. It provides a unified caching solution for cross-platform development by allowing the use of the same API across Android, iOS, and desktop applications. It supports both in-memory and file-based persistent caches, offering four eviction strategies: LRU (Least Recently Used), FIFO (First In First Out), MRU (Most Recently Used), and FILO (First In Last Out). It's fully compatible with Kotlin Coroutines for non-blocking asynchronous processing. The type-safe API design leverages compile-time type checking. For file caching, it provides automatic file management and cleanup functionality, with configurable memory usage limits and cache size settings. Despite its simple and intuitive API, it balances high performance with flexibility.
Pros and Cons
Pros
- Multiplatform Support: Same API available for Android, iOS, and desktop
- Rich Eviction Strategies: Supports four strategies - LRU, FIFO, MRU, FILO
- Memory & File Caching: Choose optimal cache type based on use case
- Coroutines Support: Non-blocking asynchronous processing
- Type Safe: Design leveraging Kotlin's type safety
- Lightweight Design: Minimal dependencies and overhead
- Auto Management: Automatic cleanup functionality for file cache
Cons
- New Library: Relatively new with limited large-scale track record
- Kotlin Only: Cannot be used with languages other than Kotlin
- Limited Documentation: Detailed documentation and samples are limited
- Ecosystem: Fewer surrounding tools compared to other cache libraries
- Limited Advanced Features: No support for advanced features like Redis Pub/Sub, Cluster, etc.
Official Links
- Kache GitHub Repository
- Kache Documentation
- Maven Central Package
- Kotlin Multiplatform
- MayakaApps Official Site
Code Examples
Gradle Dependencies Setup
// build.gradle.kts (common module)
dependencies {
// In-memory cache
implementation("com.mayakapps.kache:kache:2.1.0")
// File-based cache
implementation("com.mayakapps.kache:file-kache:2.1.0")
}
Basic Memory Cache Operations
import com.mayakapps.kache.Kache
import com.mayakapps.kache.KacheStrategy
// Memory cache initialization
val cache = Kache<String, ByteArray>(
maxSize = 5 * 1024 * 1024 // 5MB
) {
strategy = KacheStrategy.LRU
}
// Data storage and retrieval
suspend fun cacheExample() {
val key = "user_profile_123"
// Get data from cache, create new if not exists
val userData = cache.getOrPut(key) {
// Heavy processing (API calls, database access, etc.)
fetchUserDataFromApi(key)
}
// Direct value setting
cache.put("simple_key", "simple_value".toByteArray())
// Value retrieval
val cachedValue = cache.get("simple_key")
println("Cached value: ${cachedValue?.let { String(it) }}")
// Check cache size and hit ratio
println("Cache size: ${cache.size}")
println("Hit ratio: ${cache.hitRatio}")
}
File-based Cache Operations
import com.mayakapps.kache.FileKache
import com.mayakapps.kache.KacheStrategy
import java.io.File
// File cache initialization
val fileCache = FileKache(
directory = File("cache"),
maxSize = 50 * 1024 * 1024 // 50MB
) {
strategy = KacheStrategy.LRU
}
suspend fun fileCacheExample() {
val imageUrl = "https://example.com/image.jpg"
val cacheKey = "image_${imageUrl.hashCode()}"
try {
// Get data from file cache, download if not exists
val imageFile = fileCache.getOrPut(cacheKey) { tempFile ->
try {
// Download file and save to temp file
downloadImageToFile(imageUrl, tempFile)
true // Return true on success
} catch (e: Exception) {
false // Return false on failure (file will be auto-deleted)
}
}
if (imageFile != null) {
println("Image cached at: ${imageFile.absolutePath}")
// Use the file
displayImage(imageFile)
}
} finally {
// Resource cleanup
fileCache.close()
}
}
Using Different Eviction Strategies
import com.mayakapps.kache.Kache
import com.mayakapps.kache.KacheStrategy
class CacheExamples {
// LRU (Least Recently Used) - Remove least frequently used items
private val lruCache = Kache<String, UserData>(maxSize = 100) {
strategy = KacheStrategy.LRU
}
// FIFO (First In First Out) - Remove oldest items
private val fifoCache = Kache<String, ProductData>(maxSize = 200) {
strategy = KacheStrategy.FIFO
}
// MRU (Most Recently Used) - Remove most frequently used items
private val mruCache = Kache<String, TempData>(maxSize = 50) {
strategy = KacheStrategy.MRU
}
// FILO (First In Last Out) - Remove newest items
private val filoCache = Kache<String, LogData>(maxSize = 300) {
strategy = KacheStrategy.FILO
}
suspend fun demonstrateStrategies() {
// LRU cache usage example
val user = lruCache.getOrPut("user_123") {
UserData("John", "[email protected]")
}
// FIFO cache usage example
fifoCache.put("product_456", ProductData("Laptop", 999.99))
// Display cache statistics
println("LRU Cache - Size: ${lruCache.size}, Hit Ratio: ${lruCache.hitRatio}")
println("FIFO Cache - Size: ${fifoCache.size}, Hit Ratio: ${fifoCache.hitRatio}")
}
}
data class UserData(val name: String, val email: String)
data class ProductData(val name: String, val price: Double)
data class TempData(val value: String)
data class LogData(val message: String, val timestamp: Long)
Android-specific Image Cache Implementation
import android.content.Context
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import com.mayakapps.kache.Kache
import com.mayakapps.kache.FileKache
import com.mayakapps.kache.KacheStrategy
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import java.io.File
import java.io.FileOutputStream
import java.net.URL
class ImageCacheManager(private val context: Context) {
// Memory cache (for decoded Bitmaps)
private val memoryCache = Kache<String, Bitmap>(
maxSize = 20 * 1024 * 1024 // 20MB
) {
strategy = KacheStrategy.LRU
}
// File cache (for original image data)
private val fileCache = FileKache(
directory = File(context.cacheDir, "images"),
maxSize = 100 * 1024 * 1024 // 100MB
) {
strategy = KacheStrategy.LRU
}
suspend fun loadImage(url: String, maxWidth: Int, maxHeight: Int): Bitmap? {
val cacheKey = "${url}_${maxWidth}x${maxHeight}"
// First check memory cache
memoryCache.get(cacheKey)?.let { return it }
// Get image from file cache or download
val imageFile = fileCache.getOrPut(url) { tempFile ->
try {
downloadImage(url, tempFile)
true
} catch (e: Exception) {
false
}
}
return imageFile?.let { file ->
// Decode bitmap and save to memory cache
val bitmap = decodeBitmapFromFile(file, maxWidth, maxHeight)
bitmap?.let {
memoryCache.put(cacheKey, it)
it
}
}
}
private suspend fun downloadImage(url: String, file: File) = withContext(Dispatchers.IO) {
URL(url).openStream().use { input ->
FileOutputStream(file).use { output ->
input.copyTo(output)
}
}
}
private fun decodeBitmapFromFile(file: File, maxWidth: Int, maxHeight: Int): Bitmap? {
val options = BitmapFactory.Options().apply {
inJustDecodeBounds = true
}
BitmapFactory.decodeFile(file.absolutePath, options)
options.inSampleSize = calculateInSampleSize(options, maxWidth, maxHeight)
options.inJustDecodeBounds = false
return BitmapFactory.decodeFile(file.absolutePath, options)
}
private fun calculateInSampleSize(
options: BitmapFactory.Options,
reqWidth: Int,
reqHeight: Int
): Int {
val height = options.outHeight
val width = options.outWidth
var inSampleSize = 1
if (height > reqHeight || width > reqWidth) {
val halfHeight = height / 2
val halfWidth = width / 2
while (halfHeight / inSampleSize >= reqHeight && halfWidth / inSampleSize >= reqWidth) {
inSampleSize *= 2
}
}
return inSampleSize
}
fun clearCache() {
memoryCache.clear()
// File cache is automatically managed
}
fun getCacheStats(): String {
return "Memory cache: ${memoryCache.size} items, " +
"Hit ratio: ${memoryCache.hitRatio}, " +
"File cache size: ${fileCache.size} files"
}
}
iOS/Multiplatform Support Example
// commonMain/src/commonMain/kotlin/cache/DataCacheManager.kt
expect class PlatformCache
class DataCacheManager {
private val cache = Kache<String, String>(maxSize = 1000) {
strategy = KacheStrategy.LRU
}
suspend fun getCachedData(key: String): String? {
return cache.get(key)
}
suspend fun cacheData(key: String, data: String) {
cache.put(key, data)
}
suspend fun getOrFetch(key: String, fetcher: suspend () -> String): String {
return cache.getOrPut(key) {
fetcher()
}
}
}
// androidMain/src/androidMain/kotlin/cache/PlatformCache.kt
actual class PlatformCache {
// Android-specific implementation
}
// iosMain/src/iosMain/kotlin/cache/PlatformCache.kt
actual class PlatformCache {
// iOS-specific implementation
}