Nowadays, almost everyone uses WiFi at home, workplace, or even when commuting. When there is no wireless network available, we connect to the mobile network. Does it mean that we shouldn't be concerned with the availability of network when making custom mobile apps? Not really. There are many scenarios where a short period of no connectivity can ruin your app’s "smoothness".
Online first approach
The online first approach is something natural. We download data from a remote server, display a fancy loading indicator, and then present data in an attractive way. The user can see all the data, modify it, and then we send it back to the server. Here, again, we show them some kind of a progress bar. Looks really straightforward, right? And it is. That's how we usually build our apps. But let's stop for a while and see what is wrong with this approach.
First of all, we force the user to wait each time our data is fetched from the remote server. We are also wasting our network bandwidth constantly downloading data even if it hasn’t changed. Lastly, we have at least two spots in the described scenario where our network connectivity can fail.
What should we do when something goes wrong with the communication over the network? Should we display an error message and an empty view when a download has failed? It’s a popular solution, but it doesn't mean it's right.
The worst case is when a failure occurs while uploading content. Imagine writing a blog post, a Facebook comment, or an email: you click send, an error occurs, and you are not able to upload it. Will you let your user lose their data? Will you interrupt the experience of your app by displaying "Cannot upload your content. Please try again later"? That's something that should never happen. The user shouldn't take care of redownloading or reuploading data – we should. And we should start thinking about taking the "offline-first" approach to building user experience in mobile applications.
To be clear: the offline-first approach is not the universal solution to every problem you will experience with unreliable network connectivity – it heavily depends on your app’s requirements. It's more like a design approach that lets you focus on what really matters to your end user: a robust app with a great user experience.
Let’s take a look at what we can change in the online-first approach to customer-focused products.
Single source of truth
First of all, we should change our single source of truth. In the previous example, it was the remote server – we would download and upload data straight through the network. To switch to the offline-first approach, we need to have a local database. Nowadays, we have a big choice in terms of available implementations so just pick whichever fits you the best (for example Realm, Room or SharkORM).
Our database will be the only place where we pull or push data. It is much faster than the network, so it will solve the problem of forcing the user to wait each time data is being downloaded or uploaded. With a database as a single source of truth, our app shouldn't care where the data comes from – it doesn't matter if it's from the database or the network. But to make it work, we will have to take care of handling the content received from the server.
Our app now relies only on a database. We don't have loading indicators but we also have no content to load. We should somehow handle fetching the data and pulling it into the database. The details of the implementation will vary depending on an app’s requirements, but for this simple example, let's say we download all our content to the application at the first run and then re-download it in the background once a day. After a successful response from our server, we just push all the received data to our local database from where the app will pull the data. That gives us a nice app behaviour: a user sees a loading indicator only once – when they run our app for the first time. To improve the user experience, we can conceal our download process using a splash screen.
By fetching data, we’ve covered the case with downloading the content, but what about uploading it? When the user modifies something or adds new content, we also should save it to our database. The most popular technique is to save our recently edited/added data with a flag (let's name it 'pending'). We can implement a periodical sync process, which will upload all entities marked as ‘pending’ to our server. If the response is successful, we should swap our database entity with the one just received from our backend – this will guarantee data integrity. By syncing our data we won’t display any errors to our users. If the first attempt fails, our periodical syncing mechanism will upload the data as soon as possible. A good practice will be to indicate to the user that the recently added content has not been uploaded yet by showing some icon to avoid confusion, but it's up to you.
Putting the pieces together
Combining a single source of truth, data fetching and data syncing will give us a good user experience with no loading indicators and no empty views with errors. We will save network bandwidth because our data is downloaded once (and updated periodically), and the user can move through our app without any interruptions. They will be able to, without any worries, write a very long comment, and we will be sure that their content won't be lost even if the network fails – our syncing process will take care of uploading it.
The implementation details may vary depending on your app’s requirements. To help you deal with the process, here are some questions you could face dealing with the offline-first approach in your app:
How often do you need to fetch the data? Is your app’s crucial requirement to always have the most recent content? If the answer is ‘yes’, consider fetching content every time you need to display data to the user. When the request fails you should display cached results from the database anyway.
If your app is large, how will you manage to fetch data? You probably won't need to redownload all of it – think about implementing caching on your backend and download only those parts of data that were changed since the last fetch.
Do you need to upload user content right away?
Do you need to show the user that they are currently in offline mode?
Do you need to inform the user whenever data sync is in progress?
How to handle data which can be edited by many users at the same time?
How will you deal with a scenario of fetching data when your app is in the offline mode but has a mechanism to allow the user to do a refresh manually (pull to refresh for example)?