Make an easy offline working Android Application

Florent Guillemot
6 min readJan 23, 2017

--

I was a little bit bored during these Christmas vacations so I decided to create an Android application. I wanted to have something simple and easy to build. I ended creating a basic chat application using a polling system to retrieve the messages. One feature I absolutely wanted to have is the possibility to be able to browse the exchanged messages even if there is no possible communication to the server. This article will focus on how I have implemented this functionality.

Application architecture

Before jumping to the different solutions, I have explored. Let’s agree on the overall application architecture. So the communication with the server be using a JSON format over HTTP. To decode HTTP requests, I will use Retrofit v2, the Android industry standard way to do it. In order to produce reusable organized code, I will use the MVP architecture (Model View Presenter). This will also allow me to compare the testability of each solution. I ended with this architecture:

Application basic architecture
  • Server: For this part, I have used the NodeJS library Express. Note that I haven’t used any complicated database to store the messages it is only an in memory array. It is available on my Github if you want to have a look.
  • Retrofit Service: The configuration is Google’s GSON to decode the JSON and Jake Wharton’s retrofit2-rxjava2-adapter for the callback. This part will also act as application model.
  • Presenter: This part will contain application logic.
  • View: It corresponds to application activities.

The commonly used solution in Android

In the android world, to handle offline data, there are two commonly used solutions:

  • Shared preferences: a key/value system
  • SQLITE database: a small SQL database

Using these solutions will make the application architecture look like one of these schemes:

Application with cache in a dedicated service
Application with cache handled by presenter

Both of these solutions adds more code to write and maintain when you add an additional API calls. Moreover, you can have some implementation error in this new component, I have recently seen in an app a cache that was saving a GET response based on the URL only regardless of the query parameters.

Using shared preference, you also expose yourself to get some strange bugs. Indeed, it is quite common to forget to call the apply method that really saves the stored data. Moreover, the shared preference hasn’t been designed to be used as an HTTP cache.

Using a SQLITE database, it is often a very heavy solution. Indeed, if your content structure changes, you will have to create a migration script to move the new relational scheme. You will also suffer from all the concurrency problems you face with SQL database like what happens in your app if one of your threads is writing to the database and another one reading from it?

The proposed solution

Instead of building a new system why not using an existing one? In this section, I will explain you how to setup a local cache using OkHttp. It is the default HTTP client used in retrofit. If you are not using this client on your application, the explained principle can still apply to any other HTTP client as long as they implement HTTP caching. The final architecture will look like this:

Application using http cache

Implementation

The implementation principle is quite simple. When you enable the HTTP cache, by default, all requests are written into a cache. If you want to get back a previously request result from cache instead of the server, you need to add the header Cache-Control: public, only-if-cached, max-stale=[time in seconds] to the request. On the other hand, if you want your content to never be cached because, for example, it contains sensitive information, you need to add this header Cache-Control: no-cache on your request response.

With Retrofit, the minimum implementation looks like this:

  • First, you need to create a custom configuration for the HTTP client
  • Second, you need to configure a cache for this HTTP client, on this example, it is 10MB.
  • Third, you need to configure an interceptor to get the request from the cache in case, the server is not accessible.

Let’s have a deeper look at this interceptor. If the server is available the request is executed, otherwise, an exception is thrown usually it is a ConnectException or a NoRouteFoundException. If there is an exception the request is tried again, but this time with the header to get a response from the cache if there is one available.

Comparison to the other systems

Let compare the 3 solutions : HTTP cache, shared preferences and SQL database. So I have implemented the 3 solutions using the cache handled by presenter architecture for the shared preferences and SQL. The implementation test the solution for one end point. I got these values at the end:

Benchmark between the different solution

Note that due to the fact that there is no easy way to unit test an android database, I skipped this part. That’s why the SQL database solution is a bit below the other in term of code coverage.

Let’s compare the scalability of each solution, in other words, will it add more difficulties to support more requests:

  • HTTP cache: if you add another end point, you don’t have more work to do as the HTTP client is already configured. you only have to be careful about the request you don’t want to cache so it may still require a bit of work but it is negligible. I give it O(1).
  • SQL database: if you add another end point, you will have either to create a new table to store the request or modify an existing database and create a migration script from one to the new one. Moreover, you still have to find a way to unit test it and to do it. I give it O(n²).
  • Shared preferences: if you add another end point, you will have to add another key to store in the shared preferences and this for any new end point. I give it O(n).

So overall, for the use case to store a request result, it seems easier to use the HTTP cache.

Is it difficult to test ?

Let’s take the testing point of view, is it hard to test? As you will learn it in this part, it is quite easy to test.

In order to test it, I use the okhttp mock server, I created an instance of retrofit with the same configuration as the application by extracting the GSon configuration and the HTTP client to external classes. Moreover, this retrofit instance uses the okhttpmockserver as a server.

The testing follows these steps:

  • Start the mock server.
  • Create a retrofit instance with the same configuration as the app that connects to the mock server.
  • Execute a first server request test.
  • Stop the mock server.
  • Execute the same test.

If you are interested in the implementation, I made a JUnit rule to make testing easier and I have written a test using it see testGetMessages() method.

And that’s it. I hope you enjoyed this article. The full source of my application is available. If you have any questions, you can contact me using my linkedin, my twitter or in the comment section below.

--

--