Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Lightning Talk: Building Composable Caches on A...

Avatar for Matt Dolan Matt Dolan
October 26, 2017

Lightning Talk: Building Composable Caches on Android

Avatar for Matt Dolan

Matt Dolan

October 26, 2017
Tweet

More Decks by Matt Dolan

Other Decks in Programming

Transcript

  1. Request value using key In memory? On disk? Return value

    Yes No Save value in memory Yes No Save value on disk Get value from network
  2. interface Cache<Key : Any, Value : Any> { fun get(key:

    Key): Deferred<Value?> fun set(key: Key, value: Value): Deferred<Unit> } Aside: Kotlin Coroutines Deferred indicates a promise to return a value in the future
  3. fun compose(secondCache: Cache<Key, Value>): Cache<Key, Value> { return object :

    Cache<Key, Value> { override fun get(key: Key): Deferred<Value?> { val firstCache = this; return async(CommonPool) { firstCache.get(key).await() ?: let { secondCache.get(key).await()?.apply { firstCache.set(key, this).await() } } } } cache. val ueTr ansf or m er ( t r ansf or m , i nver seTr ansf or m ) f i r st Cache. com pose( secondCache) First cache Second cache miss get(key) value get(key) mappedValue value transform
  4. fun compose(secondCache: Cache<Key, Value>): Cache<Key, Value> { return object :

    Cache<Key, Value> { override fun get(key: Key): Deferred<Value?> { ... } override fun set(key: Key, value: Value): Deferred<Unit> { val firstCache = this; return async(CommonPool) { listOf(firstCache.set(key, value), secondCache.set(key, value)) .forEach { it.await() } } } } }
  5. cache. keyTr ansf or m er ( t r ansf

    or m ) cache. val ueTr ansf or m er ( t r ansf or m , i nver seTr ansf or m ) Original cache get(key) mappedValue value transform set(key, mappedValue) inverseTransform set(key, value)
  6. @Serializable data class Value(val value: Int) ... val serializer: KSerializer<Value>

    = Value::class.serializer() ... fun transform(json: String): Value { return JSON.parse(serializer, json) } fun inverseTransform(object: Value): String { return JSON.stringify(serializer, object) }
  7. cache. keyTr ansf or m er ( t r ansf

    or m ) Original cache get(mappedKey) value transform set(mappedKey, value) set(key, value) get(key) transform
  8. fun reuseInflight(): Cache<Key, Value> { return object : Cache<Key, Value>

    { val map = mutableMapOf<Key, Deferred<Value?>>() override fun get(key: Key): Deferred<Value?> { return map.get(key) ?: [email protected](key).apply { map.set(key, this) async(CommonPool) { // free up map when job is completed // regardless of success or failure join() map.remove(key) } } } … } }
  9. fun personCache(): Cache<Int, Person> { val service = retrofit.create(PersonService::class.java) val

    networkCache = Cache.fromRetrofit { id: Int -> service.getPerson(id) } val baseDiskCache: Cache<String, String> = Cache.fromDiskLruCache(DiskLruCache.open(cacheDir, 0, 1, 20)) val diskCache = baseDiskCache.keyTransformer<Int> { it.toString() } .valueTransformer(JSONSerializer( Person::class.serializer())) val memoryCache = Cache.createLruCache<Int, Person>(5) return memoryCache .compose(diskCache) .compose(networkCache) .reuseInflight() }
  10. fun personCache(): Cache<Int, Person> { val service = retrofit.create(PersonService::class.java) val

    networkCache = Cache.fromRetrofit { id: Int -> service.getPerson(id) } val baseDiskCache: Cache<String, String> = Cache.fromDiskLruCache(DiskLruCache.open(cacheDir, 0, 1, 20)) val diskCache = baseDiskCache.keyTransformer<Int> { it.toString() } .valueTransformer(JSONSerializer( Person::class.serializer())) val memoryCache = Cache.createLruCache<Int, Person>(5) return memoryCache .compose(diskCache) .compose(networkCache) .reuseInflight() }
  11. fun personCache(): Cache<Int, Person> { val service = retrofit.create(PersonService::class.java) val

    networkCache = Cache.fromRetrofit { id: Int -> service.getPerson(id) } val baseDiskCache: Cache<String, String> = Cache.fromDiskLruCache(DiskLruCache.open(cacheDir, 0, 1, 20)) val diskCache = baseDiskCache.keyTransformer<Int> { it.toString() } .valueTransformer(JSONSerializer( Person::class.serializer())) val memoryCache = Cache.createLruCache<Int, Person>(5) return memoryCache .compose(diskCache) .compose(networkCache) .reuseInflight() }
  12. fun personCache(): Cache<Int, Person> { val service = retrofit.create(PersonService::class.java) val

    networkCache = Cache.fromRetrofit { id: Int -> service.getPerson(id) } val baseDiskCache: Cache<String, String> = Cache.fromDiskLruCache(DiskLruCache.open(cacheDir, 0, 1, 20)) val diskCache = baseDiskCache.keyTransformer<Int> { it.toString() } .valueTransformer(JSONSerializer( Person::class.serializer())) val memoryCache = Cache.createLruCache<Int, Person>(5) return memoryCache .compose(diskCache) .compose(networkCache) .reuseInflight() }
  13. Caching made simple Use with LruCache, DiskLruCache & ehcache Also,

    integrates with Retrofit & LiveData Plus built-in one line data encryption for android https://github.com/appmattus/layercache