Implement Caching In Android Using RxJava Operators

First, we need to understand why caching is useful? Caching is very useful in the following situations:

  • Reduce network calls: We can reduce the network calls by caching the network response.
  • Fetch the data very fast: We can fetch the data very fast if it is cached.

There are two types of caching as follows:

  • Memory Cache: It keeps the data in the memory of the application. If the application gets killed, the data is evicted. Useful only in the same session of application usage. Memory cache is the fastest cache to get the data as this is in memory.
  • Disk Cache: It saves the data to the disk. If the application gets killed, the data is retained. Useful even after the application restarts. Slower than memory cache, as this is I/O operation.

How caching works?

The first time, the user opens the app, there will be no data in memory and no data in the disk cache. So the application will have to make a network call to fetch the data. It will fetch the data from the network and save it to the disk, keep it in the memory cache and return the data.

If the user goes to the same screen in the same session, the data will be fetched very fast from the memory cache.

If the user kills the app and restarts, it will fetch the data from the disk cache and keep it in the memory cache and return the data.

As every data comes with validity, the cache will only return the data if the data is valid at the time of retrieving.

Now, we have understood how caching works.

Let's start with the implementation part using the RxJava Operators.

For example, Data model class

public class Data {
    public String source;
}

Class to simulate memory data source

public class MemoryDataSource {

    private Data data;

    public Observable<Data> getData() {
        return Observable.create(emitter -> {
            if (data != null) {
                emitter.onNext(data);
            }
            emitter.onComplete();
        });
    }

    public void cacheInMemory(Data data) {
        this.data = data.clone();
        this.data.source = "memory";
    }

}

Class to simulate disk data Source

public class DiskDataSource {

    private Data data;

    public Observable<Data> getData() {
        return Observable.create(emitter -> {
            if (data != null) {
                emitter.onNext(data);
            }
            emitter.onComplete();
        });
    }

    public void saveToDisk(Data data) {
        this.data = data.clone();
        this.data.source = "disk";
    }

}

Class to simulate network data Source

public class NetworkDataSource {

    public Observable<Data> getData() {
        return Observable.create(emitter -> {
            Data data = new Data();
            data.source = "network";
            emitter.onNext(data);
            emitter.onComplete();
        });
    }

}

The DataSource to handle 3 data sources -

  • Memory
  • Disk
  • Network
public class DataSource {

    private final MemoryDataSource memoryDataSource;
    private final DiskDataSource diskDataSource;
    private final NetworkDataSource networkDataSource;

    public DataSource(MemoryDataSource memoryDataSource,
                      DiskDataSource diskDataSource,
                      NetworkDataSource networkDataSource) {
        this.memoryDataSource = memoryDataSource;
        this.diskDataSource = diskDataSource;
        this.networkDataSource = networkDataSource;
    }

    public Observable<Data> getDataFromMemory() {
        return memoryDataSource.getData();
    }

    public Observable<Data> getDataFromDisk() {
        return diskDataSource.getData().doOnNext(data -> 
                memoryDataSource.cacheInMemory(data)
        );
    }

    public Observable<Data> getDataFromNetwork() {
        return networkDataSource.getData().doOnNext(data -> {
            diskDataSource.saveToDisk(data);
            memoryDataSource.cacheInMemory(data);
        });
    }

}

Now using the concat and firstElement operator

Observable<Data> memory = dataSource.getDataFromMemory();
Observable<Data> disk = dataSource.getDataFromDisk();
Observable<Data> network = dataSource.getDataFromNetwork();

Observable.concat(memory, disk, network)
        .firstElement()
        .subscribeOn(Schedulers.io())
        .observeOn(AndroidSchedulers.mainThread())
        .toObservable()
        .subscribe(getObserver());

Here, we have used the concat operator to maintain the order of observables which is first check in memory, then in disk and finally in network.

So the concat operator will help us to maintain the order.

Next Rxjava operator is firstElement, this operator will make sure if we get the data from memory, it will not let the other observables(disk, network) do anything or if we get the data from the disk, it will not let the network observable do anything. So, this way no redundant work at all. This is how firstElement operator will help.

This way we can implement the caching in Android applications using the RxJava Operators.

Find the complete project here and learn RxJava.

Happy Learning :)

Team MindOrks

Also, Let’s become friends on Twitter, Linkedin, Github, Quora, and Facebook.