File-based caching example

Quite often, there is a need to cache some kind of results between the runs of the application. The most convenient way to do this on Android is a through basic file-based caching. This means that the result is computed, saved to the file, and--if it's needed again--read from the file directly instead of recomputing it again.

Usually, developers use this to save user credentials between the different sessions so that the users will not have to log in by entering their data again.

We will see how we can easily use the ObservableTranformer interface to create a universal file caching mechanism.

First of all, the FileCacheObservableTransformer should be created in the packt.reactivestocks package (or any other, according to your liking) that will host all the file-caching related code:

package packt.reactivestocks;

import android.content.Context;

import io.reactivex.ObservableTransformer;

public class FileCacheObservableTransformer<R> implements io.reactivex.ObservableTransformer<R, R> {
private final String filename;
private final Context context;

FileCacheObservableTransformer(String filename, Context context) {
this.filename = filename;
this.context = context;
}

}

As you can see, we will need the Context object to determine the appropriate caching directory and the filename itself that will be used to store the results.

Next, we will implement the .apply() method as it will be the main juice of this Transformer:

@Override
public ObservableSource<R> apply(Observable<R> upstream) {
return readFromFile()
.onExceptionResumeNext(
upstream
.take(1)
.doOnNext(this::saveToFile)
);
}

It is a very simple implementation, but it does need some explaining. First of all, the readFromFile() is called and returns an Observable that will execute successfully if the file is present. However, if the execution fails, the branch specified by .onExceptionResumeNext() will kick in and will start executing (retrieving items) from the original upstream Observable. Once that is done, the result will be saved with the .saveToFile() method.

It means that, for the first time, the readFromFile() will always fail and that will cause the original Observable to be executed and then the resulting item will be saved.

Also, we have used .take(1) to ensure that only the first items that are retrieved from the Observable are saved. Otherwise, it doesn't really make sense to keep saving items constantly, and overwriting the same file, in the same Observable stream.

Next, let's take a look at the missing pieces of code. The readFromFile() method is implemented as follows:

private Observable<R> readFromFile() {
return Observable.create(emitter -> {
ObjectInputStream input = null;
try {
final FileInputStream fileInputStream = new
FileInputStream(getFilename());
input = new ObjectInputStream(fileInputStream);
R foundObject = (R) input.readObject();
emitter.onNext(foundObject);
} catch (Exception e) {
emitter.onError(e);
} finally {
if (input != null) {
input.close();
}
emitter.onComplete();
}
});
}

It uses the already well-known technique with the Emitter interface. The saveToFile() is quite simple as well:

private void saveToFile(R r) throws IOException {
ObjectOutputStream objectOutputStream = null;
try {
final FileOutputStream fileOutputStream = new
FileOutputStream(getFilename());
objectOutputStream = new ObjectOutputStream(fileOutputStream);
objectOutputStream.writeObject(r);
} finally {
if (objectOutputStream != null) {
objectOutputStream.close();
}
}
}

As readers might have noted, this caching implementation uses the standard Java object persistence functionality that relies on the serialized interface.

You can find more information about the serialization in the official documentation at https://docs.oracle.com/javase/tutorial/jndi/objects/serial.htmland http://docs.oracle.com/javase/7/docs/api/java/io/Serializable.html.

Finally, the last missing piece is getFilename():

private String getFilename() {
return context.getFilesDir().getAbsolutePath() + File.separator +
filename;
}

It uses Android's Context to determine the appropriate directory for file caching and then the static Factory Method, as shown, to make the calls to this Transformer really simple:

public static <R> FileCacheObservableTransformer<R> cacheToLocalFileNamed(String filename, Context context) {
return new FileCacheObservableTransformer<R>(filename, context);
}

Eventually, this file caching transformer can be used as illustrated:

import static packt.reactivestocks.FileCacheObservableTransformer
.cacheToLocalFileNamed;

...

Observable.just("1")
.compose(cacheToLocalFileNamed("test", context))
.subscribe(this::log);

This cache can handle any type of object as long as it implements the Serializable interface.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
13.59.227.82