Cache interceptor para Retrofit en Kotlin

Como crear un cache interceptor para cachear peticiones a servicios apis usando Retrofit

Retrofit es una de las herramientos que suelo usar a la hora de desarrollar alguna app que necesite conectarse a una api para obtener información que mostrar. Está bastante bien, ya que definiendo unos data class con la información del api te hace todo el trabajo de mapeo.

Está bien, pero también hay que tener en cuenta que cuando se desarrolla una app que se conecta a un api usando conexión de datos no se puede estar constantemente realizando peticiones externas para obtener la información cada vez que la querramos mostrar por pantalla salvo, claro está, sea información que necesite mostrarse en tiempo real.

Una de las opciones que tenemos para cachear las peticiones con Retrofit es usar un cache interceptor. 

Para probarlo vamos a crear una pequeña aplicación con kotlin que va a obtener un listado de peliculas de the movie db (vamos a usar esta api por que es conocida y accesible para poder realizar pruebas de desarrollo. Existen muchas otras que se pueden usar de la misma manera).

Como todo, para llevar un orden vamos a usar la arquitectura MVVM. Buscaremos una ocasión para escribir sobre la arquitectura MVVM pero por ahora con saber donde está cada cosa es suficiente.

El código de ejemplo lo podéis encontrar en el repositorio de github: android-example01

Retrofit utiliza okHttpClient como cliente para las peticiones y podemos sobreescribir este client para añadirle el network interceptor que va a cachearnos las peticiones que hagamos (Podríamos atrevernos decir que sería similar a funcionamiento de un proxy reverse cache... ). 

class CacheInterceptor : Interceptor {
    override fun intercept(chain: Interceptor.Chain): Response {
        val response: Response = chain.proceed(chain.request())
        val cacheControl = CacheControl.Builder()
            .maxStale(10, TimeUnit.HOURS)
            .onlyIfCached()
            .build()
        return response.newBuilder()
            .header("Cache-Control", cacheControl.toString())
            .build()
    }
}

En la clase CacheInterceptor definimos en el response los criterios de cache. Las cabeceras de cache de la petición que vamos a realizar.

class ForceCacheInterceptor : Interceptor {
    override fun intercept(chain: Interceptor.Chain): Response {
        val builder: Request.Builder = chain.request().newBuilder()
        if (!isInternetAvailable()) {
            builder.cacheControl(CacheControl.FORCE_CACHE)
        }
        return chain.proceed(builder.build())
    }
}

fun isInternetAvailable(): Boolean {
    try {
        val command = "ping -c 1 google.com"
        return Runtime.getRuntime().exec(command).waitFor() == 0
    } catch (e: Exception) {
        return false
    }
}

En la clase ForceCacheInterceptor forzamos el uso de la cache si la conexión a internet no está disponible. Es posible añadir otros mecanismos para comprobar si los dispositivos de conexión a internet está habilitados, esto nos permite forzar el uso de la cache si está usando datos móviles, pero la para esta prueba y el objetivo de entender como se pueden interceptar y cachear las peticiones con retrofit es suficiente con comprobar con un ping si hay conexión o no.

Por último creamos nuestra class okHttp que va a sustituir a okHttpClient en el webservice.

class okHttp {
    fun okHttpClient() : OkHttpClient {
        val cacheSize = AppConstants.CACHE_SIZE.toLong()
        val localCache = Cache(AppConstants.CACHE_DIR, cacheSize)
        return OkHttpClient().newBuilder()
            .addNetworkInterceptor(CacheInterceptor())
            .addInterceptor(ForceCacheInterceptor())
            .cache(localCache)
            .build()

    }
}

Le indicamos cuales son nuestros interceptor y la ruta de nuestra cache local. El uso del directorio donde cachear las peticiones depende de configuracion en el manifets e indicar mediante el application context cual es la ruta del directorio. 

Con nuestros cache interceptor y okHttp definidos solo nos queda decirle al webservice que use este nuevo cliente para que empiece a chachear las peticiones que realicemos desde nuestra app.

object RetrofitClient {

    private val client = okHttp()

    val webService by lazy {

        Retrofit.Builder()
            .baseUrl(AppConstants.BASE_URL)
            .client(client.okHttpClient())
            .addConverterFactory(GsonConverterFactory.create(GsonBuilder().create()))
            .build().create(WebService::class.java)
    }
}

 

También te puede interesar: