Http data caching in android application - android

I have searched for this but didn't got any appropriate ans. I am developing a social android app in which some feed data need to cache show that when user open this app and there is no network connection won't get blank screen. what is the best and fast access way to cache web data.

You will most likely parse the HTTP data into your own objects. So the question will rather be how to serialize these objects, and save them. These are the most common formats for object serialization:
a plain file, such as in XML or JSON format
a SQLite database
SharedPreferences (as a Set, so this will only work well if the order of the strings is able to be rebuilt later, such as them being in alphabetical order)
(These points look common, eh, #CommonsWare ;)
Facebook, for example, uses an SQLite database, at least for their iOS app.

If you are using retrofit then you can use cache interceptors.
Firstly declare cache memory size
int cacheSize = 10 * 1024 * 1024; // this is 10MB
Then create a cache object
Cache provideCache(MyApplication context) {
Cache cache = null;
try {
cache = new Cache(new File(context.getCacheDir(), "http-cache"), cacheSize);
} catch (Exception e) {
Log.e("Cache", "Error in creating Cache!");
}
return cache;
}
Then create 2 network interceptors for HTTP client ( One for Online, one for No network case)
Interceptor provideOnlineInterceptor(MyApplication context) {
return chain -> {
Response response = chain.proceed(chain.request());
CacheControl cacheControl;
if (Utilities.isNetworkConnected(context)) {
cacheControl = new CacheControl.Builder().maxAge(0, TimeUnit.SECONDS).build();
} else {
cacheControl = new CacheControl.Builder()
.maxStale(7, TimeUnit.DAYS)
.build();
}
return response.newBuilder()
.removeHeader(HEADER_PRAGMA)
.removeHeader(HEADER_CACHE_CONTROL)
.header(HEADER_CACHE_CONTROL, cacheControl.toString())
.build();
};
Interceptor provideOfflineInterceptor(MyApplication context) {
return chain -> {
Request request = chain.request();
if (!Utilities.isNetworkConnected(context)) {
CacheControl cacheControl = new CacheControl.Builder()
.maxStale(7, TimeUnit.DAYS)
.build();
request = request.newBuilder()
.removeHeader(HEADER_PRAGMA)
.removeHeader(HEADER_CACHE_CONTROL)
.cacheControl(cacheControl)
.build();
}
return chain.proceed(request);
};
Create Httpclient
OkHttpClient provideOkHttpClient(MyApplication context) {
OkHttpClient.Builder httpClient = new OkHttpClient.Builder()
.addInterceptor(provideOfflineCacheInterceptor(context))
.addNetworkInterceptor(provideCacheInterceptor(context))
.cache(provideCache(context));
return httpClient.build();
}
Then finally add this httpclient to Retrofit instance
Retrofit provideRetrofit(OkHttpClient client,Gson gson) {
return new Retrofit.Builder().baseUrl(ApiConstants.BASE_URL)
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.addConverterFactory(GsonConverterFactory.create(gson))
.client(client)
.build();
}
Hope this will work.
Please go through below link for more detail
Network caching using interceptors

Related

How to get intermediate response from Retrofit API call?

I using Retrofit to making API call. All API call is working fine except one where its returning huge response around 15k records.
Issue is when made call progress bar is being shown infinitely until I get response. And as response too huge getting OOM exception.
As an solution I found that need to use #Streaming annotation. I used that but didn't get intermediate callback. I want API should return chunk of response one by one.
Please help me.
public static ServiceInterface getServiceAPIClient() {
HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor();
interceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
Gson gson = new GsonBuilder()
.setLenient()
.create();
OkHttpClient client = new OkHttpClient.Builder()
.addInterceptor(
new Interceptor() {
#Override
public Response intercept(Interceptor.Chain chain) throws IOException {
Request request = chain.request();
Request.Builder builder = request.newBuilder();
builder = request.newBuilder();
if (!TextUtils.isEmpty(PrefsHelper.getAccessTokenEdrm())) {
builder.addHeader(AUTHORIZATION, PrefsHelper.getAccessTokenEdrm());
}
builder.addHeader(API_VERSION, "1.0")
.addHeader("Accept", "application/json");
request = builder.build();
return chain.proceed(request);
}
}).connectTimeout(5, TimeUnit.MINUTES) .readTimeout(5, TimeUnit.MINUTES).addInterceptor(interceptor)
.build();
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(API_BASE_URL)
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())
.addConverterFactory(GsonConverterFactory.create(gson))
.client(client)
.build();
return retrofit.create(ServiceInterface.class);
}
API Method
#POST(EdrmConstants.SEARCH_DOCUMENTS)
#Streaming
Observable<ResponseBody> searchDocuments(#Body DocumentRequest documentRequest);
15k records is too match.
Retrofit needs time to make http request and makes serialization to your ResponseBody.class
I sure serialization takes main time.
I guess most right solution is to edit request on server side to split data on pages with 200-500 records.

okHttp requests + retrofit share session

I have an old app which uses raw okhttp calls and utilizes sessions.
OkHttp is setup by this code:
OkHttpClient okHttpClient = null;
try {
OkHttpClient.Builder builder = SupportRequests.getUnsafeOkHttpClient();
builder.readTimeout(5000, TimeUnit.MILLISECONDS);
builder.connectTimeout(10000, TimeUnit.MILLISECONDS);
CookieManager cookieManager = new CookieManager();
cookieManager.setCookiePolicy(CookiePolicy.ACCEPT_ALL);
builder.cookieJar(new JavaNetCookieJar(cookieManager));
okHttpClient = builder.build();
SupportRequests.setOkHttpClient(okHttpClient);
} catch (NoSuchAlgorithmException | KeyManagementException e) {
FirebaseCrash.report(e);
}
I want to switch to using retrofit. The app is quite big and contains several dozens requests. Making it in a one-time switch is not possible.
I tried to start switching and encountered a problem.
Retrofit and okHtpp raw calls do not share PHP session.
I create retrofit with following code:
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(Preferences.getInstance().server)
.addConverterFactory(GsonConverterFactory.create(SupportGson.get()))
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.client(okHttpClient)
.build();
retrofitProvider.setRetrofit(retrofit);
But sessions are different for retrofit calls and raw okhttp calls.
Any idea how to make them share session?

OkHttp3 cache seems to be unchecked with Retrofit 2

I'm trying to setup an HTTP cache using Retrofit (2.1.0) and OkHttp (3.3.1). I have seen many posts related to this topic, but none of them helped.
I wrote some unit tests to see how the cache works. It works just fine, but once integrated in my app, the magic ends. I will first show you my implementation and then explain some of my investigation.
First, here is my Retrofit instantiation :
OkHttpClient.Builder httpBuilder = new OkHttpClient.Builder();
HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor();
interceptor.setLevel(HttpLoggingInterceptor.Level.HEADERS);
OkHttpClient client = httpBuilder
.addNetworkInterceptor(INTERCEPTOR_RESPONSE_SET_CACHE)
.addNetworkInterceptor(INTERCEPTOR_REQUEST_ADD_CHECKSUM)
.addInterceptor(loggingInterceptor)
.cache(cacheHttpClient).build();
Retrofit retrofit = new Retrofit.Builder()
.addConverterFactory(GsonConverterFactory.create())
.client(client)
.baseUrl(BASE_URL)
.build();
Here is the interceptor adding a header to set cache control:
private final Interceptor INTERCEPTOR_RESPONSE_SET_CACHE = new Interceptor() {
#Override
public Response intercept(Chain chain) throws IOException {
Response response = chain.proceed(chain.request());
response = response.newBuilder()
.header("Cache-Control", "max-age=600") //+ Integer.toString(3600 * 5)
.build();
return response;
}
};
The last interceptor adds 2 URL parameters:
private static final Interceptor INTERCEPTOR_REQUEST_ADD_CHECKSUM = new Interceptor() {
#Override
public Response intercept(Interceptor.Chain chain) throws IOException {
HttpUrl url = chain.request().url();
url = url.newBuilder().addQueryParameter("rd", "random1").addQueryParameter("chk","check1").build();
Request request = chain.request().newBuilder().url(url).build();
return chain.proceed(request);
}
};
Finally, the single method of my service :
#Headers("Cache-Control: public, max-stale=500")
#GET("/get_data")
Call<DataResponse> getData(#Query("year") int year, #Query("month") int month, #Query("day") int day);
About my investigation, I setup an interceptor logger (app side, not network) to see what is happening. I can see lines such as "Cache-Control: public, max-stale=500" in my logs. This means (at least to me) that the header should give an opportunity to the OkHttp client to check the cache.
The cache itself seems to be correctly initialised. When I create it, I force the initialisation and log all the urls present in the cache. Here is how it is implemented:
File httpCacheDirectory = new File(getCacheDir(), "responses");
httpCacheDirectory.getParentFile().mkdirs();
int cacheSize = 10 * 1024 * 1024; // 10 MiB
Cache cache = new Cache(httpCacheDirectory, cacheSize);
try {
cache.initialize();
Iterator<String> iterator = cache.urls();
Log.i(TAG, "URLs in cacheHttpClient : ");
while (iterator.hasNext()) {
Log.i(TAG, iterator.next());
}
} catch (IOException e) {
e.printStackTrace();
Log.i(TAG, "CACHE NOT INIT");
}
When I launch my app with Wifi available, I get the expected responses. Then I kill my app, disable Wifi and relaunch the app. I expect the cache to serve data at this moment. But it fails and I can only see OkHttp printed lines in logs :
HTTP FAILED: java.net.UnknownHostException: Unable to resolve host
"my-domain.com": No address associated with hostname
Last thing, in RFC 2616, one can read :
max-stale : Indicates that the client is willing to accept a response
that has exceeded its expiration time. If max-stale is assigned a
value, then the client is willing to accept a response that has
exceeded its expiration time by no more than the specified number of
seconds. If no value is assigned to max-stale, then the client is
willing to accept a stale response of any age.
When I don't specify an value, it actually works (I get a response even when the Wifi is down). For now this is the only way I found to make it "work". So maybe I just misunderstand the cache-control directive !?
At this point I'm really confused. I really would like to be able to use OkHttp cache system, but somehow I'm missing something.
Thank you for reading all that text !
Use this method to create cached okkhttpclient
private OkHttpClient createCachedClient(final Context context) {
File httpCacheDirectory = new File(context.getCacheDir(), "cache_file");
Cache cache = new Cache(httpCacheDirectory, 20 * 1024 * 1024);
OkHttpClient okHttpClient = new OkHttpClient();
okHttpClient.setCache(cache);
okHttpClient.interceptors().add(
new Interceptor() {
#Override
public com.squareup.okhttp.Response intercept(Chain chain) throws IOException {
Request originalRequest = chain.request();
String cacheHeaderValue = isOnline(context)
? "public, max-age=2419200"
: "public, only-if-cached, max-stale=2419200" ;
Request request = originalRequest.newBuilder().build();
com.squareup.okhttp.Response response = chain.proceed(request);
return response.newBuilder()
.removeHeader("Pragma")
.removeHeader("Cache-Control")
.header("Cache-Control", cacheHeaderValue)
.build();
}
}
);
okHttpClient.networkInterceptors().add(
new Interceptor() {
#Override
public com.squareup.okhttp.Response intercept(Chain chain) throws IOException {
Request originalRequest = chain.request();
String cacheHeaderValue = isOnline(context)
? "public, max-age=2419200"
: "public, only-if-cached, max-stale=2419200" ;
Request request = originalRequest.newBuilder().build();
com.squareup.okhttp.Response response = chain.proceed(request);
return response.newBuilder()
.removeHeader("Pragma")
.removeHeader("Cache-Control")
.header("Cache-Control", cacheHeaderValue)
.build();
}
}
);
return okHttpClient;
}
private boolean isOnline(Context context) {
ConnectivityManager connectivity = (ConnectivityManager) _context.getSystemService(Context.CONNECTIVITY_SERVICE);
if (connectivity != null) {
NetworkInfo[] info = connectivity.getAllNetworkInfo();
if (info != null)
for (int i = 0; i < info.length; i++)
if (info[i].getState() == NetworkInfo.State.CONNECTED) {
return true;
}
}
return false;
}
Call createCachedClient() method to create OkHttpClient add this client to retrofit
OkHttpClient okHttpClient = createCachedClient(MainActivity.this);
Retrofit retrofit=new Retrofit.Builder()
.client(okHttpClient)
.baseUrl(API)
.addConverterFactory(GsonConverterFactory
.create()).build();
Add this permission to manifest
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
If internet is available first time it will call the service and cache the request,next time onwards upto 2419200 milliseconds it will use cache to give response.it won't hit server upto 2419200 milliseconds even if device if offline.

Retrofit 2 caching - chain of interceptors

I need to implement basic caching of API responses. I've made a little playground project that calls GitHub API and caching was successful (I've used Charles to verify that). However when I transferred this solution to my target project caching didn't work anymore. Could multiple interceptors in the chain be the reason?
Code from playground project (working):
Interceptor (same for target project):
public class CacheControlInterceptor implements Interceptor {
#Override
public Response intercept(Chain chain) throws IOException {
Response response = chain.proceed(chain.request());
return response.newBuilder()
.header("Cache-Control", "only-if-cached")
.build();
}
}
Cache and client declaration:
long SIZE_OF_CACHE = 10 * 1024 * 1024; // 10 MB
final Cache cache = new Cache(new File(getCacheDir(), "retrofit_cache"), SIZE_OF_CACHE);
OkHttpClient.Builder client = new OkHttpClient.Builder().cache(cache);
client.networkInterceptors().add(new CacheControlInterceptor());
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://api.github.com/users/")
.addConverterFactory(GsonConverterFactory.create())
.client(client.build())
.build();
Screen from debugging of CacheControlInterceptor:
screen
Code from target project (NOT working):
Cache and client declaration:
private OkHttpClient provideOkHttpClient() {
HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor();
loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
OkHttpClient.Builder okhttpClientBuilder = new OkHttpClient.Builder();
okhttpClientBuilder.connectTimeout(30, TimeUnit.SECONDS);
okhttpClientBuilder.readTimeout(30, TimeUnit.SECONDS);
okhttpClientBuilder.writeTimeout(30, TimeUnit.SECONDS);
okhttpClientBuilder.addInterceptor(loggingInterceptor);
okhttpClientBuilder.addInterceptor(new JwtRenewInterceptor(getUserSession()));
okhttpClientBuilder.addInterceptor(new AutoLoginInterceptor(getUserSession()));
okhttpClientBuilder.addNetworkInterceptor(new CacheControlInterceptor());
long SIZE_OF_CACHE = 10 * 1024 * 1024; // 10 MB
final Cache cache = new Cache(new File(getCacheDir(), "retrofit_cache"), SIZE_OF_CACHE);
okhttpClientBuilder.cache(cache);
return okhttpClientBuilder.build();
}
Screen from debugging of CacheControlInterceptor: screen
If you want apply some headers to all requests using OkHttp cache you should use Application interceptor, not network interceptor. Otherwise, you are not giving cache mechanism a chance to return cached responses.
It's nicely illustrated on OkHttp wiki
So most probably what is happening in your code is that you let Cache to store responses but you never use them since requests going to Cache are missing only-if-cached header.
Try
okhttpClientBuilder.addInterceptor(new CacheControlInterceptor());
Actually the mistake was caused by my poor reasoning about http headers. I thought that method addHeader or header will simply add key Cache-Control and then value only-if-cached. However it adds only value! And since in my target project's API there was no header key Cache-Control (unlike in GitHub API) there was no place for value only-if-cached to be stored.

If-None-Match doesn't get passed in my request

I have seen good long discussion on this topic and it is claimed to be fixed in 2.3.0.
Here is the combination I am using
compile 'com.squareup.retrofit2:retrofit:2.0.0-beta4'
compile 'com.squareup.retrofit2:converter-gson:2.0.0-beta4'
compile 'com.squareup.okhttp3:logging-interceptor:3.0.1'
compile 'com.squareup.okhttp3:okhttp:3.2.0'
logs I see against received response, have Etag; but subsequent request I do doesn't have If-None-Match passed in its header.
I tested it by inserting If-None-Match explicitly by my code, caching worked and response was expected one. So there is surely something wrong with version of libraries I am using or something not good about my code.
Here I am setting up okClient.
HttpLoggingInterceptor httpLoggingInterceptor = new HttpLoggingInterceptor();
httpLoggingInterceptor.setLevel(HttpLoggingInterceptor.Level.HEADERS);
okhttp3.OkHttpClient okClient = new okhttp3.OkHttpClient.Builder()
.addInterceptor(new HeaderInterceptor())
.addInterceptor(httpLoggingInterceptor)
.cache(createCacheForOkHTTP())
.connectTimeout(5, TimeUnit.MINUTES)
.readTimeout(5, TimeUnit.MINUTES)
.build();
retrofit = new Retrofit.Builder()
.baseUrl(AppConfig.API_URL)
.addConverterFactory(GsonConverterFactory.create())
.client(okClient)
.build();
My header interceptor contains the logic that is pretty much focused to my API itself. Here it is
private class HeaderInterceptor
implements Interceptor {
private String generateAuthHeader(AuthResponse accessToken) {
if (accessToken == null) {
return "";
}
return String.format("Bearer %s", accessToken.getAccessToken());
}
#Override
public okhttp3.Response intercept(Chain chain)
throws IOException {
Request request = chain.request();
final String authorizationValue = generateAuthHeader(runtime.getPrefAccessToken());
if (!TextUtils.isEmpty(authorizationValue)) {
request = request.newBuilder()
.addHeader(AppConfig.API_KEY_AUTHORIZATION, authorizationValue)
.addHeader(AppConfig.API_KEY_ACCEPT, AppConfig.API_ACCEPT)
.build();
//.addHeader("If-None-Match", "a69385c6d34596e48cdddd3ce475d290")
} else {
request = request.newBuilder()
.addHeader(AppConfig.API_KEY_CONTENT_TYPE, AppConfig.API_CONTENT_TYPE)
.addHeader(AppConfig.API_KEY_ACCEPT, AppConfig.API_ACCEPT)
.build();
}
okhttp3.Response response = chain.proceed(request);
return response;
}
}
And here is the method using which I am setting up cache.
private Cache createCacheForOkHTTP() {
Cache cache = null;
cache = new Cache(App.getInstance().getBaseContext().getCacheDir(), 1024 * 1024 * 10);
return cache;
}
Looking for some quick and effective response as I already have spent reasonable time finding the solution but no luck.
Thanks
Your code seems to be working, I haven't tried it out but i faced the same issue few weeks ago. It turned out that it was because of the log from retrofit did not show the if-none-match header. But when i tried to intercept the request using proxy and redirect the request to my laptop first (i was using mitmproxy app), the if-none-match header appeared.
Anyway, if you look into /okhttp3/internal/http/CacheStrategy.java inside this method private CacheStrategy getCandidate(), you will see that OkHttp3 is actually using the etag & if-none-match header properly.
Hope this clarifies.

Categories

Resources