Skip to content
Caching made simple for Android and Java.
Kotlin Java
Branch: master
Clone or download
Permalink
Type Name Latest commit message Commit time
Failed to load latest commit information.
.circleci
.idea Initial setup of cache2k Oct 27, 2017
README Updating readme to show usage Oct 30, 2017
gradle/wrapper Upgrade to Kotlin 1.3 Oct 31, 2018
layercache-android-encryption Upgrade to Kotlin 1.3 Oct 31, 2018
layercache-android-livedata Upgrade to Kotlin 1.3 Oct 31, 2018
layercache-android Upgrade to Kotlin 1.3 Oct 31, 2018
layercache-cache2k Upgrade to Kotlin 1.3 Oct 31, 2018
layercache-ehcache Upgrade to Kotlin 1.3 Oct 31, 2018
layercache-retrofit Upgrade to Kotlin 1.3 Oct 31, 2018
layercache-serializer Upgrade to Kotlin 1.3 Oct 31, 2018
layercache Upgrade to Kotlin 1.3 Oct 31, 2018
testutils Upgrade to Kotlin 1.3 Oct 31, 2018
.gitignore Adding .DS_Store to gitignore Oct 20, 2017
.travis.yml Upgrade to Kotlin 1.3 Oct 31, 2018
LICENSE Initial commit Oct 19, 2017
README.md Optimisations for circleci build Oct 31, 2017
bintray.gradle Update to Kotlin 1.2.60 and androidx (#16) Aug 16, 2018
build.gradle Revert android plugin to latest release Oct 31, 2018
detekt.yml Initial commit Oct 20, 2017
dokka-android.gradle Tidied up copyright messages Nov 4, 2017
dokka.gradle Tidied up copyright messages Nov 4, 2017
gradle.properties Upgrading library versions Nov 3, 2017
gradlew Initial commit Oct 20, 2017
gradlew.bat Initial commit Oct 20, 2017
jacoco-android.gradle Tidied up copyright messages Nov 4, 2017
jacoco.gradle Tidied up copyright messages Nov 4, 2017
proguard-rules.pro Update to Kotlin 1.2.60 and androidx (#16) Aug 16, 2018
settings.gradle Tidied up copyright messages Nov 4, 2017

README.md

layercache CircleCI Coverage Status

Caching made simple for Android and Kotlin.

An important component of building offline-first architectures is to implement caching.

An offline-first architecture initially tries to fetch data from local storage and, failing that, requests the data from the network. After being retrieved from the network, the data is cached locally for future retrieval. This helps to ensure that network requests for the same piece of data only occur once—with subsequent requests satisfied locally.

At its simplest, a cache is nothing more than a key-value store with a getter and setter.

interface Cache<Key : Any, Value : Any> {
    fun get(key: Key): Deferred<Value?>
    fun set(key: Key, value: Value): Deferred<Unit>
}

The real power comes when we are able to compose two caches into a new cache. A memory cache should have a single responsibility to store data in memory, and a disk cache a single responsibility to store data on disk.

val cache = memoryCache.compose(diskCache)

For more details read Caching made simple on Android or watch the talk from droidcon London 2017.

Getting started

Base module

compile 'com.appmattus:layercache:<latest-version>'

Composing two caches

When get is called on the composed cache, the first cache will be checked and if available returned. If not the second cache will be checked and if available set in the first cache and returned.

composedCache: Cache<Key, Value> = firstCache.compose(secondCache)

Transforming values

Transform values between data types. This can be used for serialisation and encryption amongst other things. i.e. Cache<Key, Value> → Cache<Key, MappedValue>

For two way transformations:

val cache: Cache<Key, Value> = ...
val valueTransform: Cache<Key, MappedValue> = cache.valueTransform(transform, inverseTransform)

// or

val cache: Cache<Key, Value> = ...
val valueTransform: Cache<Key, MappedValue> = cache.valueTransform(TwoWayTransform)

One way transforms return a Fetcher instead of Cache, but otherwise work in the same way. A Fetcher simply implements no-op for set and evict.

val fetcher: Fetcher<Key, Value> = ...
val valueTransform: Cache<Key, MappedValue> = cache.valueTransform(transform)

// or

val fetcher: Fetcher<Key, Value> = ...
val valueTransform: Cache<Key, MappedValue> = cache.valueTransform(OneWayTransform)

Transforming keys

Transform keys to a different data type. i.e. Cache<Key, Value> → Cache<MappedKey, Value>

val cache: Cache<Key, Value> = ...
val keyTransform: Cache<MappedKey, Value> = cache.keyTransform(transform)

// or

val cache: Cache<Key, Value> = ...
val keyTransform: Cache<MappedKey, Value> = cache.keyTransform(OneWayTransform)

Re-using in flight requests

If a get request is already in flight then this ensures the original request is returned. This may be necessary for disk and network requests along with transformations that take time to execute.

val newCache: Cache<Key, Value> = cache.reuseInflight()

Retrofit module

compile 'com.appmattus:layercache-retrofit:<latest-version>'

Given a Retrofit service that returns a Call, we can turn this into a Cache with Cache.fromRetrofit:

interface RetrofitService {
    @GET("get/{key}")
    fun aRequest(@Path("key") key: Key): Call<Value>
}

val service = retrofit.create(RetrofitService::class.java)

...

val cache : Cache<Key, Value> = Cache.fromRetrofit { key: Key ->
    service.aRequest(key)
}

Serializer module

compile 'com.appmattus:layercache-serializer:<latest-version>'

Configures a transformation from JSON to a serialisable data class and vice-versa. This is useful to store data on disk or in a database. i.e. Cache<Key, String> → Cache<Key, Value>

@Serialize
data class Value(val value)

val cache: Cache<Key, String> = ...

val objectCache: Cache<Key, Value> = cache.jsonSerializer(Value::class.serializer())

Android base module

compile 'com.appmattus:layercache-android:<latest-version>'

LruCache

val memoryCache: Cache<Key, Value> = Cache.createLruCache(maxSize: Int)

// or

val memoryCache: Cache<Key, Value> = Cache.fromLruCache(...)

DiskLruCache

val memoryCache: Cache<String, String> = Cache.createDiskLruCache(directory: File, maxSize: Long)

// or

val memoryCache: Cache<String, String> = Cache.fromDiskLruCache(...)

Android LiveData module

compile 'com.appmattus:layercache-android-livedata:<latest-version>'

Given a cache we can convert it for use with LiveData. This makes the getter return a LiveDataResult which can be one of Loading, Success or Failure.

val liveDataCache = cache.toLiveData()

liveDataCache.get("key").observe(owner) { liveDataResult ->
    when (liveDataResult) {
        is LiveDataResult.Loading -> {
            // display in progress
        }
        is LiveDataResult.Success -> {
            // display liveDataResult.value
        }
        is LiveDataResult.Failure -> {
            // display liveDataResult.exception
        }
    }
}

Download Download

Available from jcenter()

dependencies {
    compile 'com.appmattus:layercache:<latest-version>'

    // To use with the Kotlin serializer
    compile 'com.appmattus:layercache-serializer:<latest-version>'

    // Provides support for ehcache
    compile 'com.appmattus:layercache-ehcache:<latest-version>'

    // Enables converting Retrofit calls to Cache
    compile 'com.appmattus:layercache-retrofit:<latest-version>'

    // Provides LruCache & DiskLruCache support for Android
    compile 'com.appmattus:layercache-android:<latest-version>'

    // Provides one-line String encryption for Android
    compile 'com.appmattus:layercache-android-encryption:<latest-version>'

    // Provides conversion from Cache into LiveData for Android
    compile 'com.appmattus:layercache-android-livedata:<latest-version>'
}

Contributing

Please fork this repository and contribute back using pull requests.

All contributions, large or small, major features, bug fixes, additional language translations, unit/integration tests are welcomed.

License License

Copyright 2017 Appmattus Limited

Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.

You can’t perform that action at this time.