When we develop our applications we mostly have the best internet connections and we tend to not think about the number of requests the app will make to out back end server once it is live in production. A good caching layer in our applications can vastly improve the user experience for people with not so good internet connections and at the same time take off some load of your server . It can also save you a couple of bucks when you are paying for cloud services. What is caching ? Caching is storing data in your application memory that you would normally fetch over the network (like an downloaded image from an URL) so that you can access it faster the next time it is required. temporarily A couple of points to keep in mind when you are caching data : A cache the device memory. The cache architecture should be able to decide what data is needed and judiciously keep only the useful ones in memory. should not hog up A cache the main source . The stored data should be refreshed after a amount of time to make sure the data is fresh. should not replace A cache layer from the main application logic. The application should only be concerned with handling the data and the cache should take care of refreshing , storing , deleting or fetching the data from the source. should be abstracted Keeping all these factors in mind , I decided to create an open source caching library for android that will allow you to create a caching layer in your app in minutes. 😊 In this post I will show how you can cache responses that we can fetch from a server using a call. We will do this to implement the cache layer . The entire source code for this example is available . GET using just annotations here Let's get coding Add the following in your root buid.gradle file { { google() jcenter() maven { url } } } allprojects repositories 'https://jitpack.io' Add the kotlin-kapt gradle plugin for annotation processing in your application buid.gradle file apply plugin: apply plugin: apply plugin: apply plugin: 'com.android.application' 'kotlin-android' 'kotlin-android-extensions' "kotlin-kapt" Add the cold storage dependencies . Check the for the latest release and other features. repository implementation kapt implementation "com.github.crypticminds.ColdStorage:coldstoragecache:2.0.1" "com.github.crypticminds.ColdStorage:coldstoragecompiler:2.0.1" "com.github.crypticminds.ColdStorage:coldstorageannotation:2.0.1" Now we will write the logic for fetching data from the server. For this example I am using ,which basically returns the parameters passed to the endpoint back to the caller. In our application we will just show the response received from the server. “https://httpbin.org/get” com.arcane.coldstorageannotation.Refrigerate java.net.URL { : String { url = textResponse = URL(url).readText() textResponse } : String { url = textResponse = URL(url).readText() textResponse } } import import class MakeRemoteCall /** * A method that makes a call to the "https://httpbin.org/get" * endpoint. This endpoint simply returns the arguments that were passed to it. * For the caching logic the parameter passed to the endpoint will act as * the key of the cache. * * Since there is only one parameter we don't need to specify the the keys * field in the annotation. */ @Refrigerate(operation = ) "CallToServiceA" fun makeRemoteCallToServiceA (value: ) String val "https://httpbin.org/get?param1= " $value val return /** * We will use the same endpoint but this time we will pass 3 parameters. * However , out cache key should only be the first 2. * We can configure the "keys" field in the annotation by specifying * the variable names that should act as the key of the cache. * In this case "parameter1" and "parameter2" */ @Refrigerate( operation = , timeToLive = 10000, keys = [ , ] ) "CallToServiceB" "parameter1" "parameter2" fun makeRemoteCallToServiceB (parameter1: , parameter2: , parameter3: ) String String String val "https://httpbin.org/get?param1= &param2= &param3= " $parameter1 $parameter2 $parameter3 val return I am annotating the methods using the " annotation and passing the and parameters to it. @Refrigerate" timeToLive , keys operation This attribute decides how long a data in the cache will remain "fresh" otherwise the cache will fetch it from the source instead of the cache. timeToLive : This parameter will accept the list of variables that the method takes as input , which will be considered as the keys of the cache. For example in function parameter1 and parameter2 will uniquely determine the result of the function and hence they will be considered the keys of the cache. If I pass "foo" and "bar" as the parameter values , the cache will store the corresponding result for this and in the next invocation of this function with "foo" and "bar" it will return the value from the cache.In case all the parameter will determine the result of a function , they keys attribute need not be specified. keys : remoteCallToServiceB This is a mandatory attribute for the annotation. Operation will uniquely identify each method that has been annotated. operation : YOUR CACHE LAYER IS READY . (Yes it was this simple) Under the hoods When you annotate the methods using " " it generated a new class called " " which will wrap all the functions with caching logic. Now instead of calling the functions in the class you will be using class to invoke the functions . Lets see how below :- @Refrigerate GeneratedCacheLayer.kt MakeRemoteCall GeneratedCacheLayer android.os.Bundle android.widget.Button android.widget.TextView androidx.appcompat.app.AppCompatActivity com.arcane.coldstoragecache.callback.OnOperationSuccessfulCallback com.arcane.coldstorageexamples.remotecall.MakeRemoteCall com.arcane.generated.GeneratedCacheLayer (), OnOperationSuccessfulCallback<String?> { makeremoteCall = MakeRemoteCall() button: Button firstRemoteCall: TextView secondRemoteCall: TextView valueArray = arrayListOf( , , ) counter = { .onCreate(savedInstanceState) setContentView(R.layout.activity_main) button = findViewById(R.id.button) firstRemoteCall = findViewById(R.id.remotecall1) secondRemoteCall = findViewById(R.id.remotecall2) button.setOnClickListener { (counter % == ) { GeneratedCacheLayer.makeRemoteCallToServiceA( valueArray.random(), makeremoteCall, ) } { GeneratedCacheLayer.makeRemoteCallToServiceB( valueArray.random(), valueArray.random(), , makeremoteCall, ) } counter += } } { runOnUiThread { firstRemoteCall.text = s } } { runOnUiThread { secondRemoteCall.text = s } } { (operation) { -> populateFirstTextView(output!!) -> populateSecondTextView(output!!) } } } import import import import import import import : class MainActivity AppCompatActivity /** * The make remote call object. * * MakeRemoteCall for details. */ @see private val private lateinit var private lateinit var private lateinit var private val "a" "b" "c" private var 1 override fun onCreate (savedInstanceState: ?) Bundle super /** * On button click random values from the array will be used to make the * remote calls. * The counter variable is used to control the two calls are made alternatively * on button clicks. */ if 2 0 this else "constantvalue" this 1 /** * Populate the first text view with the response. */ private fun populateFirstTextView (s: ) String /** * Populate the second text view with the response. */ private fun populateSecondTextView (s: ) String /** * The callback implementation via which the result is * returned by the generated cache layer. */ override fun onSuccess (output: ?, operation: ) String String when "CallToServiceA" else Import the file into the class where you want to invoke the function . In my case it is the main activity com.arcane.generated.GeneratedCacheLayer import Now call the function using this class GeneratedCacheLayer.makeRemoteCallToServiceA( valueArray.random(), makeremoteCall, ) this I am passing two new parameters in this generated function other than the ones that the original method already expects . One, is the instance of class since the generated function will use the original method to refresh the data in the cache and second, a call back interface ( ) that I implemented and which will be used to pass the value to the main activity from the cache. This is required since all cache operations are performed asynchronously and the callback will be needed to pass the data to the UI thread. makeRemoteCall OnOperationSuccessfulCallback<String?> To access this generated class normally like any other you can try running the app after applying all the annotations. Your IDE will automatically index the new class once it is generated. Moment of truth After you are done calling the functions using the generated class, run your app and check the logs. You will see logs like these in your window logcat 2020–01–14 02:26:45.132 21851–21911/com.arcane.coldstorageexamples I/COLD_STORAGE: 2020–01–14 02:26:46.004 21851–21911/com.arcane.coldstorageexamples I/COLD_STORAGE: 2020–01–14 02:26:46.283 21851–21911/com.arcane.coldstorageexamples I/COLD_STORAGE: 2020–01–14 02:26:46.835 21851–21911/com.arcane.coldstorageexamples I/COLD_STORAGE: 2020–01–14 02:26:46.835 21851–21911/com.arcane.coldstorageexamples I/COLD_STORAGE: 2020–01–14 02:26:48.243 21851–21911/com.arcane.coldstorageexamples I/COLD_STORAGE: 2020–01–14 02:26:48.993 21851–21911/com.arcane.coldstorageexamples I/COLD_STORAGE: 2020–01–14 02:26:48.993 21851–21911/com.arcane.coldstorageexamples I/COLD_STORAGE: 2020–01–14 02:26:50.140 21851–21911/com.arcane.coldstorageexamples I/COLD_STORAGE: 2020–01–14 02:26:50.708 21851–21911/com.arcane.coldstorageexamples I/COLD_STORAGE: 2020–01–14 02:26:50.708 21851–21911/com.arcane.coldstorageexamples I/COLD_STORAGE: Cache hit Cache miss due to stale data Putting value in cache Cache hit Cache hit Putting value in cache Cache hit Cache hit Putting value in cache Cache hit Cache hit Your cache layer is fully functional now . 👍