Data Caching in Kotlin: How to Speed Up Your App 10x Without Magic
Imagine: you open your favorite app, and it takes 5 seconds to load, showing a placeholder and an endless spinner. Sound familiar? Now imagine the same program opening instantly, even without the internet. The secret is smart data caching. And today, we'll break down how to implement it in Kotlin so your code is not only fast but also elegant.
Kotlin is a language loved for its conciseness and safety. But even with the coolest language, you can write a sluggish application if you don't think about caching. In this article, we'll go from a simple in-memory cache to advanced solutions with Redis and DiskLruCache. You'll learn how to cache data in Kotlin so that users forget about loading times, and the server forgets about unnecessary requests.
Why Cache Data in Kotlin at All?
Caching is like a good habit: hard at first, then indispensable. Here are three reasons why your Kotlin application needs a cache:
- Speed: data is read from memory in microseconds, not seconds like from a server or disk.
- Traffic savings: fewer API requests mean less money spent on servers and mobile data.
- Offline mode: users love it when an app works even in the subway without a connection.
But there's a catch: incorrect caching can lead to memory leaks (OutOfMemoryError — hello from the past) or displaying outdated data. So let's figure out how to do it right.
In-memory Cache: The Simplest Way to Speed Up a Kotlin App
Let's start with the basics — caching in RAM. It's like putting the tools you need on your desk instead of running to the garage for them every time.
The simplest option is to use a HashMap or ConcurrentHashMap (if working with multithreading). But in Kotlin, we can make it more elegant:
class SimpleCache<K, V>( private val maxSize: Int = 100) { private val cache = LinkedHashMap<K, V>(0, 0.75f, true) // access-order
fun get(key: K): V? { return cache[key] }
fun put(key: K, value: V) { if (cache.size >= maxSize) { val oldest = cache.keys.firstOrNull() oldest?.let { cache.remove(it) } } cache[key] = value }
fun clear() { cache.clear() }}This cache automatically removes the oldest item when it reaches the limit. But there's a problem — it doesn't account for data time-to-live. For example, you cached a list of currency exchange rates, but an hour later the rate changed, and the user sees old data. Not good.
Adding TTL (Time To Live)
data class CacheEntry<T>( val data: T, val expirationTime: Long)
class TTLCache<K, V>( private val ttlMillis: Long = 60_000, // 1 minute private val maxSize: Int = 100) { private val cache = LinkedHashMap<K, CacheEntry<V>>(0, 0.75f, true)
fun get(key: K): V? { val entry = cache[key] ?: return null if (System.currentTimeMillis() > entry.expirationTime) { cache.remove(key) return null } return entry.data }
fun put(key: K, value: V) { if (cache.size >= maxSize) { val oldest = cache.keys.firstOrNull() oldest?.let { cache.remove(it) } } cache[key] = CacheEntry(value, System.currentTimeMillis() + ttlMillis) }}Now data lives exactly as long as needed. Perfect for caching API responses where data changes infrequently (e.g., a list of product categories).
Caching in Android: DiskLruCache and Room
On mobile devices, memory is a limited resource. If your