Blog Infos
Author
Published
Topics
, , ,
Published

Let’s be honest, we’ve all been there — frantically refreshing our app, watching the loading spinner spin endlessly as we wait for that critical API response. It’s a dance we’ve perfected, isn’t it? The rhythmic tapping of our fingers, the subtle eye rolls, and the not-so-subtle muttering under our breath. “Come on, just give me the data already!”

When it comes to building applications that interact with external APIs, caching can be a game-changer. API calls can often be slow, resource-intensive, and subject to rate limiting by the API providers. By implementing a robust caching mechanism, you can dramatically reduce the number of API calls, lower response times, and provide a smoother overall user experience.

One of the ways to solve the problem of repeated API calls is to cache/store their responses locally. In this article, we’re going to take a look at how we can use OkHttp library’s CacheControl class to store API responses with a time validity.

https://github.com/ishanvohra2/findr?source=post_page—–1384a621c51f——————————–

Implementing the Cache
Defining the cache size and instance

To be able to store the responses for API calls locally in a cache, first, we need to define it and let our client know about the same. In the following snippet, we’re defining the cache by using the Cache class present in okhttp library. We’ve set the maximum size of this cache to 5 MB. Then we use the cache() function while initializing our okhttpclient parameter.

// Defining a cache of 5 MB size
val cacheSize = (5 * 1024 * 1024).toLong()
//Initializing instance of Cache class
val myCache = Cache(context.cacheDir, cacheSize)
//defining okhttpclient instance
val okHttpClient = OkHttpClient.Builder()
.cache(myCache)
.build()
view raw OkHttpClient.kt hosted with ❤ by GitHub
Defining rules for our cache

Let’s define some basic rules for network cache based on whether the device is connected to the internet or not as follows:

  1. If the device is connected to the internet: If the last API response was retrieved less than 30 minutes ago then show the cached response otherwise, get the new response and store it in the cache.
  2. If the device is offline: Use the API response up to 1 day old to keep the app functioning.

You can always define different and more nuanced rules for complex scenarios for your projects but for the sake of simplicity, we’ll use the aforementioned.

The first step is to check if the user has an internet connection or not. To do that we can use the ConnectivityManager class to gather data and check if the user is connected to the internet or not.

Let’s define a function called hasNetwork() as shown below:

fun hasNetwork(context: Context): Boolean {
val connectivityManager = context
.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
val nw = connectivityManager.activeNetwork ?: return false
val actNw = connectivityManager.getNetworkCapabilities(nw) ?: return false
return when {
actNw.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) -> true
actNw.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) -> true
actNw.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET) -> true
actNw.hasTransport(NetworkCapabilities.TRANSPORT_BLUETOOTH) -> true
else -> false
}
}
view raw CheckNetwork.kt hosted with ❤ by GitHub

We check if the user is connected to a WiFi, cellular or Bluetooth network and return true or false based on the same.

To implement the rules, we have defined for our cache, we’ll be using a component of OkHttp library called an Interceptor. An interceptor is a powerful mechanism that allows you to intercept, process, and potentially modify HTTP requests and responses before they are sent or received by your application. They act like a middleman in the communication flow, giving you more control over how OkHttp handles network requests.

Within the interceptor, we will provide instructions on when to use the cached response to our okHttpClient instance.

Let us add the interceptor using the function called addInterceptor() while building our client instance. We can modify our API request in this function. Within the lambda function, we can get the request object and modify it before finally sending it to the server. To add caching functionality, we use the function called cacheControl() which takes in parameters belonging to theCacheControl class.

The first rule we need to implement is if the device has internet, then use the response from 30 minutes ago (if there is one), otherwise get the new response. We use the maxAge() function to implement the same.

The second rule we need to implement is if the device is disconnected, then use the response which is at most 1 day old. For this, we use maxStale() function.

Job Offers

Job Offers

There are currently no vacancies.

OUR VIDEO RECOMMENDATION

, ,

Migrating to Jetpack Compose – an interop love story

Most of you are familiar with Jetpack Compose and its benefits. If you’re able to start anew and create a Compose-only app, you’re on the right track. But this talk might not be for you…
Watch Video

Migrating to Jetpack Compose - an interop love story

Simona Milanovic
Android DevRel Engineer for Jetpack Compose
Google

Migrating to Jetpack Compose - an interop love story

Simona Milanovic
Android DevRel Engin ...
Google

Migrating to Jetpack Compose - an interop love story

Simona Milanovic
Android DevRel Engineer f ...
Google

Jobs

// Defining a cache of 5 MB size
val cacheSize = (5 * 1024 * 1024).toLong()
//Initializing instance of Cache class
val myCache = Cache(context.cacheDir, cacheSize)
//defining okhttpclient instance
val okHttpClient = OkHttpClient.Builder()
.cache(myCache)
.addInterceptor { chain ->
var request = chain.request()
request = if (hasNetwork(context))
request
.newBuilder()
.cacheControl(
CacheControl.Builder()
.maxAge(30, TimeUnit.MINUTES)
.build()
)
.build()
else
request
.newBuilder()
.cacheControl(
CacheControl.Builder()
.maxStale(1, TimeUnit.DAYS)
.build()
)
.build()
chain.proceed(request)
}
.build()
view raw gistfile1.txt hosted with ❤ by GitHub

Difference between maxAge() and maxStale()

Both max-stale and max-age are directives used in HTTP caching to control how fresh a cached response can be. However, they have distinct meanings:

  • max-age: This directive specifies the maximum age of a cached response that a client considers to be fresh. It’s expressed in seconds. A response older than max-age seconds is considered stale by the client, and the client will request a fresh response from the server if available.
  • max-stale: This directive tells the client that it’s willing to accept a stale response, even if it’s older than the max-age specified by the server. It also specifies the maximum amount of time the response can be stale. It’s also expressed in seconds.

And that is it, we have now implemented the caching system in our OkHttpClient implementation. Now, it’s time to build your project and provide a fast and reliable experience for your customers/users.

Don’t worry, if you’re not using OkHttp library directly and instead using Retrofit library as a wrapper around it. Use the following snippet to implement caching in your project!

class RetrofitClient(private val context: Context) {
val cacheSize = (5 * 1024 * 1024).toLong()
val instance: Api by lazy {
val myCache = Cache(context.cacheDir, cacheSize)
val okHttpClient = OkHttpClient.Builder()
.cache(myCache)
.addInterceptor { chain ->
var request = chain.request()
request = if (hasNetwork(context))
request
.newBuilder()
.cacheControl(
CacheControl.Builder()
.maxAge(30, TimeUnit.MINUTES)
.build()
)
.build()
else
request
.newBuilder()
.cacheControl(
CacheControl.Builder()
.maxStale(1, TimeUnit.DAYS)
.build()
)
.build()
chain.proceed(request)
}
.addInterceptor(HttpLoggingInterceptor().apply {
this.level = HttpLoggingInterceptor.Level.BODY }
)
.build()
val retrofit = Retrofit.Builder()
.baseUrl("BASE_URL")
.addConverterFactory(GsonConverterFactory.create())
.client(okHttpClient)
.build()
retrofit.create(Api::class.java)
}
}

Now, you can go ahead and run your project on a physical device or emulator. To inspect if the caching is indeed working, use the App Inspection tab in Android Studio or use OkHttp’s logging interceptor to log all network calls.

Conclusion

For API responses that do not change over a long period can be cached via the above technique and would save a lot of resources. Software development always involves making decisions based on certain trade-offs between time and space. In this instance, we traded off for a little bit of space (for storing the API responses), to save time.

If you would like to read more on how to improve performance in your app. Check out this article on debouncing!

https://towardsdev.com/increase-performance-in-your-app-using-debouncing-0cdb29023f82?source=post_page—–1384a621c51f——————————–

Thanks for reading!

This article is previously published on proandroiddev.com

YOU MAY BE INTERESTED IN

YOU MAY BE INTERESTED IN

blog
It’s one of the common UX across apps to provide swipe to dismiss so…
READ MORE
blog
Hi, today I come to you with a quick tip on how to update…
READ MORE
blog
Automation is a key point of Software Testing once it make possible to reproduce…
READ MORE
blog
Drag and Drop reordering in Recyclerview can be achieved with ItemTouchHelper (checkout implementation reference).…
READ MORE

Leave a Reply

Your email address will not be published. Required fields are marked *

Fill out this field
Fill out this field
Please enter a valid email address.

Menu