Offline & Furious: Tips to accelerate your offline mode development

Have you ever been asked “How difficult is for -insert feature- to work offline?” and you are like…

Me IRL but actually handsome

Luckily, we were asked that question several times at Wolox and are here to share what we learned with you. Plus, you get a free ride on the feels train courtesy of our friend over here, Paul.

Before we start, a relevant heads up. I’m an Android Developer and a Kotlin lover so most examples will be written in it. Promise I will keep the post as Android free as possible. I will mostly use it to exemplify.

Now, buckle up and get ready to jump start your offline support development with these 3 tips.

Tip #1: Offline & online are two separate concerns

We are going to apply a common advice in the software development community: Separation of concerns.

There are several reasons why online and offline should be treated differently: distinct interfaces (ie: SQL, Filesystem vs. HTTP, REST), resources stored externally vs. internally, and the list goes on and on. Initially, one may think “Hey, I just transform some HTTP requests to SQL calls and that’s it!” but nothing is further from the truth. In certain scenarios, they may look like siblings but they are definitely not twins.

Let’s look at a concrete example. Say we retrieve some Car data, in this case represented as a data class, through a web API:

data class Car(val id: Int, val brand: String, val model: String)

You may be tempted to make this data available offline by promoting the class to FlexibleCar looking like this:

data class FlexibleCar(val externalId: Int?, val localId: Int?, val brand: String, val model: String)

Notice anything odd here?

Paul certainly noticed!

There’s no way to correctly identify this object since we can’t be certain whether it came from a local or a remote source. Thus, we have to make both externalId and localId optional, by making them nullable.

This forces the user to think about 4 cases:

  • externalId present and localId present.
  • externalId present and localId absent.
  • externalId absent and localId present.
  • externalId absent and localId absent. A “should never happen” case that we have to guard against anyway.

Not the ideal scenario. A better approach is to keep the original Car definition for external sources and add a new & shiny LocalCar:

data class LocalCar(val id: Int, val brand: String, val model: String)

By doing this, we gain not having the id as an optional property and the freedom of adding and/or removing whatever data we want from that class without breaking the contract we had with the remote API.

This is one tiny example with one property. Imagine several. This is why we believe that you should actively look at opportunities to separate concerns like the one above.

Tip #2: Choose your local storage wisely

Carefully consider the type and amount of data to manage and the use case of your offline support. The right tool for the right job.

You should be this confident when making the decision

We are going to teach you the mindset we use to make this type of decision by going over three use cases and the path is taken for each of them.


Manage basic data

The perfect example being user settings. In this case, a simple key-value should suffice. For example, Android’s Shared Preferences or Unity’s PlayerPref.

Persist large data in a particular format, possibly known

Classic example: Images. However, it could be other stuff like JSON files. In this scenario, sounds like leveraging the job to the filesystem is a good choice.

Manage possibly complex records of data that may be related to each other

We branch out from here with trigger questions:

  • Are the records heavily related and/or your team is familiar with SQL? A SQL database seems like a perfect match. The actual technology of choice is rather difficult to pinpoint but we almost always go with a SQL ORM. In the Android ecosystem, Google is pushing its own solution with Room. It provides a high degree of customization by allowing you to tinker with the actual SQL executed for the queries. Depending on how comfortable your team feels with SQL, you may want to choose a less manual library, for example GreenRobot’s GreenDAO.
  • Are the records not that related and/or your team doesn’t fancy SQL that much? noSQL seems to better fit. This field has some well-known player in its tracks, some of which are likely to be known across different techs. Take for instance Realm, a polished cross-platform noSQL solution that offers great tooling and support designed to be offline first.
  • Your team doesn’t want to worry about the offline mechanisms and just care about them working offline? You should pick a tool that isolates you from the process. Here you have several options that should be carefully reviewed but we are going to pick Firebase to make the point. Now, Firebase is a whole infrastructure suite that solves a plethora of problems. One of these being local storage and realtime querying. The catch is that your data must go through it and the service is not cheap. However, if you don’t have the resources to develop a solution on your own and/or you are not interested in pursuing it, then Firebase may be your savior.

Notice that none of these is a silver bullet. In fact, we ended up combining several of them in a single application depending on the issue at hand. We need to understand the context in order to make the right call.

Tip #3: Follow the Rule of Three

Three strikes and you refactor!

This one bit me particularly hard once.

It’s quite difficult to know how a subsystem should look like before actually coding some use cases. This is especially true when the level of uncertainty is high and/or your team isn’t familiar with the problem domain. Thus, this rule spans over the whole team. If necessary, iterate the solution with several other teammates (besides your Code Reviewers, because you already are doing code review… Right? 👀).

It’s not the same to:

  • Store external records and querying them
  • Creating records locally and querying them

For both, you need a way to save the data in the device. But on one hand, you may need to pull information periodically to keep up to date. On the other hand, the mechanism creates information that external entities are not aware of and those entities may need to be in sync with. These cases may not hit you until you code them and knowing how they should look beforehand is not an easy task.

Don’t generalize until you know the problem well enough. If you want to read more about this magic number, follow this link. You will be amazed by the level of belief the author has on this rule.

Conclusion

As with everything in software development, it’s difficult to ascertain what’s the right solution for each scenario. What we can guarantee is that offline mode is not a clear definition of a requirement. It means different things depending on the circumstances and everyone should be aware of this.

We hope that these three tips orient your future offline mode development. We think that these can trigger the most important questions that people need to be asked to define the problem rigorously.


That’s all! Appreciate you getting through the article and we hope you continue to read our posts.