I would like to have something like Android Compose's remember(key) { init } in plain JVM Kotlin. So some sort of Lazy delegate but on every access it calls a key function and if its return value changed the value is recalculated. Is there a simple standard library functionality for achieving this or do I need to create a custom class?
let `lazy` recalculate when value changed
119 views Asked by thornySoap At
3
There are 3 answers
0
On
lazy cannot do this, but any simple Cache should be able to. Simply set its max size to one, and any new key you get from the Cache will replace the previous value.
Here is an example based on the Caffeine caching library:
import com.github.benmanes.caffeine.cache.Caffeine
class CachedValue<K, V>(loader: (K) -> V) {
private val cache = Caffeine.newBuilder().maximumSize(1).build(loader)
fun get(key: K): V = cache.get(key)
}
First of all, there is no easy and straightforward way to solve this. The problem is that we can't observe the value of a regular property or a function just like that, so we don't know when exactly to re-calculate the value.
There are multiple possible solutions, depending on our needs. One is to provide a way to invalidate the cached value, so create a very similar utility to
Lazy, but with additionalinvalidate()function.Another solution is what you suggested: specify some kind of a key which we use to determine if we need to re-calculate. Code to acquire the current key should be very lightweight as it will be invoked with each access to the property. Example implementation:
Usage:
Please note this implementation is not thread safe.
In practice, such solution is pretty limited, we can use it in very specific cases only as we need a key to identify the change. This kind of problem is more commonly solved by another solution - by creating observable variables. We have a source variable A of a very specific type which allows to observe it for changes. Then a target variable B observes it, so it knows when to re-calculate.
Over years we improved this concept greatly: we started with a simple Observer Pattern, but it had many drawbacks. Then there are Reactive Streams. Kotlin improved on this concept by utilizing coroutines - it provided flows:
Please note this is entirely different concept, everything works asynchronously. We can rewrite the example and ask the
targetvariable directly, as in the previous example, but we don't have guarantees on when exactly after setting thesource, thetargetwill be updated. This is intentional behavior.