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.
What is caching ?
- A cache should not hog up the device memory. The cache architecture should be able to decide what data is needed and judiciously keep only the useful ones in memory.
- A cache should not replace the main source . The stored data should be refreshed after a amount of time to make sure the data is fresh.
- A cache layer should be abstracted 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.
Let's get coding
Add the following in your root buid.gradle file
allprojects {
repositories {
google()
jcenter()
maven { url 'https://jitpack.io' }
}
}
Add the kotlin-kapt gradle plugin for annotation processing in your application buid.gradle file
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
apply plugin: "kotlin-kapt"
Add the cold storage dependencies . Check the repository for the latest release and other features.
implementation "com.github.crypticminds.ColdStorage:coldstoragecache:2.0.1"
kapt "com.github.crypticminds.ColdStorage:coldstoragecompiler:2.0.1"
implementation "com.github.crypticminds.ColdStorage:coldstorageannotation:2.0.1"
Now we will write the logic for fetching data from the server.
import com.arcane.coldstorageannotation.Refrigerate
import java.net.URL
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): String {
val url = "https://httpbin.org/get?param1=$value"
val textResponse = URL(url).readText()
return textResponse
}
/**
* 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 = "CallToServiceB", timeToLive = 10000,
keys = ["parameter1", "parameter2"]
)
fun makeRemoteCallToServiceB(parameter1: String, parameter2: String, parameter3: String): String {
val url = "https://httpbin.org/get?param1=$parameter1¶m2=$parameter2¶m3=$parameter3"
val textResponse = URL(url).readText()
return textResponse
}
}
YOUR CACHE LAYER IS READY . (Yes it was this simple)
Under the hoods
import android.os.Bundle
import android.widget.Button
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
import com.arcane.coldstoragecache.callback.OnOperationSuccessfulCallback
import com.arcane.coldstorageexamples.remotecall.MakeRemoteCall
import com.arcane.generated.GeneratedCacheLayer
class MainActivity : AppCompatActivity(), OnOperationSuccessfulCallback<String?> {
/**
* The make remote call object.
*
* @see MakeRemoteCall for details.
*/
private val makeremoteCall = MakeRemoteCall()
private lateinit var button: Button
private lateinit var firstRemoteCall: TextView
private lateinit var secondRemoteCall: TextView
private val valueArray = arrayListOf("a", "b", "c")
private var counter = 1
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
button = findViewById(R.id.button)
firstRemoteCall = findViewById(R.id.remotecall1)
secondRemoteCall = findViewById(R.id.remotecall2)
/**
* 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.
*/
button.setOnClickListener {
if (counter % 2 == 0) {
GeneratedCacheLayer.makeRemoteCallToServiceA(
valueArray.random(),
makeremoteCall,
this
)
} else {
GeneratedCacheLayer.makeRemoteCallToServiceB(
valueArray.random(),
valueArray.random(),
"constantvalue", makeremoteCall, this
)
}
counter += 1
}
}
/**
* Populate the first text view with the response.
*/
private fun populateFirstTextView(s: String) {
runOnUiThread {
firstRemoteCall.text = s
}
}
/**
* Populate the second text view with the response.
*/
private fun populateSecondTextView(s: String) {
runOnUiThread {
secondRemoteCall.text = s
}
}
/**
* The callback implementation via which the result is
* returned by the generated cache layer.
*/
override fun onSuccess(output: String?, operation: String) {
when (operation) {
"CallToServiceA" -> populateFirstTextView(output!!)
else -> populateSecondTextView(output!!)
}
}
}
Import the file into the class where you want to invoke the function . In my case it is the main activity
import com.arcane.generated.GeneratedCacheLayer
Now call the function using this class
GeneratedCacheLayer.makeRemoteCallToServiceA(
valueArray.random(),
makeremoteCall,
this
)
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
2020–01–14 02:26:45.132 21851–21911/com.arcane.coldstorageexamples I/COLD_STORAGE: Cache hit
2020–01–14 02:26:46.004 21851–21911/com.arcane.coldstorageexamples I/COLD_STORAGE: Cache miss due to stale data
2020–01–14 02:26:46.283 21851–21911/com.arcane.coldstorageexamples I/COLD_STORAGE: Putting value in cache
2020–01–14 02:26:46.835 21851–21911/com.arcane.coldstorageexamples I/COLD_STORAGE: Cache hit
2020–01–14 02:26:46.835 21851–21911/com.arcane.coldstorageexamples I/COLD_STORAGE: Cache hit
2020–01–14 02:26:48.243 21851–21911/com.arcane.coldstorageexamples I/COLD_STORAGE: Putting value in cache
2020–01–14 02:26:48.993 21851–21911/com.arcane.coldstorageexamples I/COLD_STORAGE: Cache hit
2020–01–14 02:26:48.993 21851–21911/com.arcane.coldstorageexamples I/COLD_STORAGE: Cache hit
2020–01–14 02:26:50.140 21851–21911/com.arcane.coldstorageexamples I/COLD_STORAGE: Putting value in cache
2020–01–14 02:26:50.708 21851–21911/com.arcane.coldstorageexamples I/COLD_STORAGE: Cache hit
2020–01–14 02:26:50.708 21851–21911/com.arcane.coldstorageexamples I/COLD_STORAGE: Cache hit
Your cache layer is fully functional now . 👍