Offline support: “Try again, later”, no more.

Yonatan V. Levin
Mar 2, 2017 · 6 min read

I have the privilege of living in a country where 4G network and strong Wifi is almost everywhere — at home, at work, even at the basement of my friend’s apartment.
And somehow, I still manage to get this:

Image for post
Image for post

or this

Image for post
Image for post

Maybe it’s because my Pixel phone is playing with me? Uh… no.

The internet connection is the most unstable thing that I have ever used. While in 95% of the time it works well, and I’m successfully able to stream my favorite music without any problem and always, always when I’m standing in an elevator and trying to send a message — it fails on me.

We, as developers live in an environment where a strong connection isn’t an issue, but the fact is — it is. Even more — just as in Murphy’s law — it will hurt your users exactly when they most need your App to work — and work fast.

Being an Android user and observing this “Try again” happening in many of my installed apps. I struggled to do something about it, at least in my app.

There are a lot of great talks about offline support, for example, Yigit Boyar and his IO talk (you even can spot me at the first row cheering him).


Our precious App

Image for post
Image for post

Finally, after starting my own startup KolGene, I got my chance. In startups, as most of you know, you start by building your first MVP and testing your assumptions. The process is so crucial and hard, with so many things that could go wrong, that losing even a single customer because of an offline issue is totally unacceptable.

Every customer we lose costs us a lot of money.
If there were leaving because the experience of using the application was bad — well, it’s not even an option.

Our app usage is pretty simple: A clinician creates a request for genetic tests on mobile app, the relevant laboratories receive a message, submit offers, clinician receives those offers and chooses the best offer based on their needs.

Image for post
Image for post

When we discussed various UX solutions, we decided on the following: No loading bar at all — even if it’s very beautiful.

The app should work smoothly without putting the user in a “waiting” state.

So basically what we want to achieve, is that internet connectivity shouldn’t matter — the app will always work.

And the result was:

Image for post
Image for post

When the user is in offline mode, he submits the request and … it’s submitted.
The only small reminder about being offline is small icon of “syncing” status at the top right corner. Once he comes online, the app will post his request to the server regardless if it is in background or foreground.

Image for post
Image for post

Same goes for every other network request-except for registration and sign In.

So how did we do it?

First we started by completely separating our view, logic and persistence model. As Yigit Boyar says:

Act locally, sync globally

It means that your model should be persistent and will be updated from the outside world. The data from model should propagate asynchronously using callbacks/events to the presenter and later to the view. Remember - the view is dumb and only reflects what we have in our model. No loading dialogs. Nothing. The view reacts to user and passes the interaction result through the presenter to the model and later, receives the next state to show.

Image for post
Image for post
High Level Architecture

For local storage we use SQLite. On top of it we decided to wrap it in a Content Provider because of its ContentObserver capability for events.
ContentProvider is a nice abstraction for Data access and manipulation.

Why not RxJava? Well, it’s completely different topic. In short — for a startup, when you need to move as fast as possible and the project changes hands every couple months — we decided to leave it as simple as possible.
Besides, I love ContentProvider, and there are a lot of additional capabilities: auto-initialization , running in a separate process and a custom search interface.

For background sync jobs, we choose to use GCMNetworkManager. If you’re not familiar with it — it’s enables scheduling tasks/periodic tasks to be executed when certain specific conditions met, like internet connection for example and it lives really well with Doze mode.

So the architecture looks like this:

Image for post
Image for post
Low level architecture

The flow: Create Order and Sync it.

Step 1: Presenter creates new order and sends it for insert via ContentResolver to Content Provider.

Image for post
Image for post
Created order sent via ContentResolver

Step 2: Content Provider inserts into the local database and notifies all observables that there is a new order created with status “pending”.

Image for post
Image for post

Step 3: Our background service that was registered to observe changes in Order table by URI — gets notified and starts the specific service for this task.

Image for post
Image for post
ID of orders bypassed to new service to handle sync with server

Step 4: The service obtains the data from DB and tries to sync it over the network. When network request succeeds— the order is updated with status “synced” via ContentResolver.

Image for post
Image for post

Step 5: If the request fails, it will schedule GCMNetworkManager one-time task with .setRequiredNetwork(Task.NETWORK_STATE_CONNECTED) and order id.

When the criteria is met (the device is connected to the internet and no doze mode), GCMNetworkManager calls onRunTask() and the app will try to sync our order once again. If it fails again — it will reschedule it.

Image for post
Image for post

Once the order is synced, a background service or GCMNetworkManager will update the status of local order via ContentResolver with the status “synced”.

Image for post
Image for post
Image for post
Image for post

Of course this type of architecture is not bulletproof. It requires that you handle all possible edge cases, for example what if you schedule a task to update an existing order on the server, but it was canceled/changed by an admin on the server side? What if they change the same property? What should happen if the first update was made by the user, or by an admin?
Some of them we solved immediately. Some of them we left in production (they are really rare). The different approaches that we took to solve these isseus, I will share in one of my next articles.

And there is definitely some room for improvements in our codebase as Fred says:

Even the best planning is not so omniscient as to get it right the first time.
— Fred Brooks

But we continue to struggle to improve it and make usage of our KolGene App delightful and full of joy to the users.

Image for post
Image for post
Sharing is caring!

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch

Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore

Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store