React Native Offline

Jun 24, 2020 | Mobile, Technical Blog

At TangoCode we love working with react js and redux, it is our by default frontend framework and recently we had the opportunity to take that skill to the mobile platform with react-native.

We worked with MAPSCorps to support their research summer programs, where students visit places from their locations, collecting all sorts of data, run surveys, etc; but one key feature for this app was the offline support since many of these places could be remote and may not have cellular coverage. In this blog, I will like to drive you through the scope of the problem we needed to solve, and later how we approached it.

Also you can find the Android and iOS here:

Android: https://play.google.com/store/apps/details?id=com.mapscorp

iOS: https://apps.apple.com/us/app/mapscorps/id1511797571

Scope of the offline solution

As you may know, supporting offline in a mobile app can have multiple flavors and levels of challenges, so it is important to clarify the scope of the solution we need it to implement and some of the assumptions we made.

The main idea is that the mappers (students that visit the places) should be able to completely collect the data in an online and/or offline environment, and if the latter is true, they should be able to synchronize back with the server once they go online.

For the synchronization piece, we took the assumption that no conflicts will need to be managed since a place can only be mapped by a specific mapper based on their assignments, these assignments change but at a given time only one mapper is assigned to a place.

Our Architecture

So before we dig into how we solved this problem, let’s briefly review the architecture of our mobile app:

So as you can see is pretty much a regular redux solution, we use the Containers and Presenter pattern and Sagas for async actions.

Our solution

So as any offline solution the first step is to be able to identify if the app is online or offline, for that we use the @react-native-community/netinfo and created a NetworkProvider component using the Context API to make the network status available across the application.

In addition to this, we need to tell the user whether they were online or offline. We created a Component that subscribed to the Network Provider and displayed and hid when necessary. The only thing left to do was add this component to any screen we need to display it.

Next step, we now need to persist the data we have already downloaded, in case we go offline, so it does not vanish when the phone is turned off or the app is taken out of memory by the user or the os.

For this we use redux-persist.

So far so good, so now to the most critical pieces, how did we let the user save a place offline?

Following common sense, what can we do when we need to save something for later? You store it, perfect we just needed to store that place in a list and later sync that list to the server; for this, we created an additional reducer to keep track of this pending offline actions, the idea behind it is that we could just store the redux actions, since they are just js objects and dispatch them later when we are online

Ok great but… synchronization needs to be as transparent as possible, so from their point of view the user that place was mapped/visited and their app should reflect that, meaning other screens of the app, like the dashboard, the progress of the assignments, etc, need to reflect that; basically the state of the app needs to be updated as if it had triggered the asynchronous request to the server a successfully, only that no request was sent and it needs to be store for later.

In order to create this illusion in the state of the app, we hijack this request just before it was triggered, and when we had all the information about it so we can trigger it later, in the Sagas.

As you can see from the code above we first check if we are online or offline if the later we do not trigger the request but do dispatch the synchronous action that updated the reducer and therefore the state of the app; in addition, we store the whole action as is so we can just dispatch it later when online.

The last thing to check is that when we trigger action for synchronization purposes the local state should not be updated because it already did.

And that’s it, thanks for making it this far, and if you have any questions or comments we will love to hear back from you.

Cheers.