Retrofit 2 load cache before network call - android

i am using retrofit 2 to make my API calls, but my issue is that API response takes time to show the response, is there any chance I can store the data and load that cache and show it and then meanwhile calling the network API.
Like for example :
the first hit -> Make Network Call - render the output to the screen say a list view --> store the response in the cache
next time the user comes on screen --> load the cache and render it to screen --> make the network call --> refresh the adapter with changes
am referring to one of a gist link
https://gist.github.com/Tetr4/d10c5df0ad9218f967e0

Yes, there are many solutions for doing that.
This is the solution if you use RxJava (which works well with retrofit)
First is using concat like:
Observable<Data> source = Observable
.concat(memory, disk, network).first();
memory, disk, and network are Observables. It will take first. That means if the cache is available, use cache, else disk, else network.
The problem in that is that it will never go to the network once it has a cache. That's why you should do something like that:
getRemoteItems().publish { Flowable.merge(it, getLocalItems().takeUntil(it)) }
That will get the remote and local at the same time, but stop the local one once the remote data has been fetched. You can use doOnSuccess to fill the local database with the remote data using
Yes, there are many solutions for doing that.
First is using concat like:
Observable<Data> source = Observable
.concat(memory, disk, network).first();
memory, disk, and network are Observables. It will take first. That means if the cache is available, use cache, else disk, else network.
The problem in that is that it will never go to network once it has a cache. That's why you should do something like that:
getRemoteItems()
.doOnSuccess { storeToLocal(it) }
.publish { Flowable.merge(it, getLocalItems().takeUntil(it)) }
That means that it gets the remote items to the same time as it tries to get the cache. If it was able to fetch remote data it stores the data to the cache.
If you're using the first example without first() it will deliver in the worst case 3 times Data but wait until the observable before is finished. That means it will try memory, once memory call onComplete() it goes to disk. If the disk is completed it try the network.
Observable<Data> source = Observable
.concat(memory, disk, network)

You could first cache all the responses:
public static Retrofit getAdapter(Context context, String baseUrl) {
OkHttpClient.Builder okHttpClient = new OkHttpClient().newBuilder();
Cache cache = new Cache(getCacheDir(), cacheSize);
okHttpClient.cache(cache).build();
Retrofit.Builder retrofitBuilder = new Retrofit.Builder();
retrofitBuilder.baseUrl(baseUrl).client(okHttpClient);
return retrofitBuilder.build();
}
And in the next network call, you could check if the response has changed. If so, then you get that new response, if not then you don't need to refresh your adapter:
if (response.isSuccessful() &&
response.raw().networkResponse() != null &&
response.raw().networkResponse().code() ==
HttpURLConnection.HTTP_NOT_MODIFIED) {
// the response hasn't changed, so you do not need to do anything
return;
}
// otherwise, you can get the new response and refresh your adapter
...
Following #vsync comment, I just updated this answer. I hope it's better now.
References: https://android.jlelse.eu/reducing-your-networking-footprint-with-okhttp-etags-and-if-modified-since-b598b8dd81a1

Related

RxJava2 concat operator doesn't return appropriate sequence

When I make a request to the network, if an error occurs, then I will return data from the cache and the error. But sometimes I don’t get data from the cache, but I get only an error. The first time I launch the application, I always get only an error. If I call the getDashboard method once or several times, then everything is fine.
Here is a piece of code.
.onErrorResumeNext(throwable -> {
return Observable.concat(Observable.just(fromCache), Observable.error(throwable));
});
Full code here
https://gist.github.com/githubgist123/7e027675bb4db07fef606e23f39f8a96
Sorry, but the way you're doing your caching is wrong in my opinion. If you request twice consecutively and the cache is empty you'll end up with two network requests running and racing each other to write to the cache. You don't want that.
What you need is:
Observable.concat(
cache(),
network().share().doOnNext(setCache(...)).onErrorReturn(...)
.first()
)
You might think of synchronized your cache too or make it thread-safe by using the proper data structure.

Which is the best way to retrieve data first from a cache then from a network call in RxJava ?(concat concatEager,merge)

For the first time I want to retrieve data from server cache it and next times show data on UI from local storage and request from server and update local storage and UI as
I have tried
(getCachedData()).concatWith(getRemoteData())
getCachedData returns Single
return apiSeResource.getData()
.doAfterSuccess { response ->
saveData(response.body())
}
}
.onErrorReturn {
return#onErrorReturn emptyList()
}
}```
The problem with `concat` is that the subsequent observable doesn't even start until the first Observable completes. That can be a problem. We want all observables to start simultaneously but produce the results in a way we expect.
I can use `concatEager` : It starts both observables but buffers the result from the latter one until the former Observable finishes.
Sometimes though, I just want to start showing the results immediately.
I don't necessarily want to "wait" on any Observable. In these situations, we could use the `merge` operator.
However the problem with merge is: if for some strange reason an item is emitted by the cache or slower observable after the newer/fresher observable, it will overwrite the newer content.
So none of mentioned above solution is not proper ,what is your solution?
Create 2 data sources one local data source and one remote and use the flatMap for running the Obervables. You can publish the data from the cache and when u get data from remote save data to cache and publish.
Or you can also try Observable.merge(dataRequestOne, dataRequestTwo) . run both the Observables on different threads

RxJava Only want to call cache when api call is success

I am very new to RxJava and can't seem to find figure out the solution to this use case. I have been researching on this for 2 days now and no luck.
I have 2 Singles, remote and cache, to register a user in my app
I first call remote which saves the user data on a server, and returns a custom code to indicate successfully saved. I only want to call cache after I have checked the custom code from remote and gotten a success. If custom code comes as failure, I want to return that, and not go to the cache at all
The operator, which you're looking for, is flatMap. Example:
remoteApi.login().flatMap(new Function<String, SingleSource<String>>() {
#Override public SingleSource<String> apply(String response) throws Exception {
if (response.equals("success")) {
// do what you want to do with cache
return cache.save(response);
}
return Single.just(response);
}
}).subscribe(yourObserver);
Don't forget to use subscribeOn and observeOn...

Retrofit + RxJava - cache / publish and retry on error?

I have the following requirement:
Multiple observers (fragments) need to subscribe to a data source.
Activity will start a network request. Once the request is successful, each observer will receive the result.
I've trying to do it using cache / publish operators, but the issue is when initial request returns an error. At this point I wish to reset the stream and subsequent calls to the method should run a new network request instead of returning an error each time.
Here's what I have currently.
private Flowable<List<Data>> dataObservable;
private Flowable<List<Data>> getData(){
if(dataObservable == null){
dataObservable = apiService.getData()
.doOnError(throwable -> {
dataObservable = null;
})
.cache();
}
return dataObservable;
}
This works, but the code feels wrong. There's got to be a better way.
You can define the observable ahead of time, and it won't actually do anything until something subscribes to it. That's one less null value to worry about.
You can use the retry() operator, or a variant of it, to automatically retry the network operation on an error.
Finally, the cache() operator will ensure that only one network connection subscription is active. Each subscriber will get any updates from the observable, and will be oblivious to any network errors experienced.
Flowable<List<Data>> dataObservable = apiService.getData()
.retry()
.cache();
Apparently there is no operator for that.
The issue was discussed at Observable, retry on error and cache only if completed
and Plato created a nice tiny lib for that
platoblm/rx-onerror-retry-cache.

RxJava network requests and caching

I am seeking an example of a flow I'm trying to implement with help of RxJava.
Suppose I want to show a list of data. The flow should look something like this:
Read cache. If it contains the data, show it;
Send an API request to the server:
If it returned the data, then cache it and show it.
If it returned and error and there was no cached data, then show an error.
If it returned and error and there was something cached, then do nothing.
Right now I have a method that does something similar (with lots of inspiration from Jake's u2020). The main difference is that it uses in-memory caching, which means there's no need for a separate Observable for reading from cache and it can be done synchronously.
I don't know how to combine two observables (one for reading from cache and the other for API call) and obtain the flow described above.
Any suggestions?
I think I solved my problem. The observable chain looks like so:
apiCall()
.map(data -> dataInMemory = data)
.onErrorResumeNext(t -> data == null ?
Observable.just(Data.empty()) : Observable.empty())
.startWith(readDataFromCache().map(data -> dataInMemory = data))
.subscribeOn(ioScheduler)
.observeOn(uiScheduler)
.subscribe(dataRequest);
The main point is, that if readDataFromCache() throws an error, it will call onCompleted() without calling onError(). So it should be a custom Observable which you can control.
Data.empty() is a stub for my data - the Subscriber should treat it as an error.
dataInMemory is a member in my controller which acts as in-memory cache.
EDIT: the solution doesn't work properly. The completion of one use case (see comment) is not achieved.
EDIT 2: well, the solution does work properly after some tweaking. The fix was returning different types of observables depending on the state of in-memory cache. Kind of dirty.
Here is my solution:
readDataFromCache().defaultIfEmpty(null)
.flatMap(new Func1<Data, Observable<Data>>() {
#Override
public Observable<Data> call(final Data data) {
if (data == null) {
// no cache, show the data from network or throw an error
return apiCall();
} else {
return Observable.concat(
Observable.just(data),
// something cached, show the data from network or do nothing.
apiCall().onErrorResumeNext(Observable.<Data>empty()));
}
}
});
I don't add the subscribeOn and observeOn because I'm not sure readDataFromCache() should use ioScheduler or uiScheduler.

Categories

Resources