Return

Offline-First Application Design

In this day and age of high-speed cellular internet and public Wi-Fi, there’s almost always a way to connect your smartphone or tablet to the internet. It’s a creature comfort we’ve grown used to and enjoy. But what about those times when there’s no Wi-Fi router nearby to connect to, and you’re out of range of your cellular service provider’s towers? If you don’t account for this in your application’s architecture, your application isn’t going to be of much use to your users when they’re off the grid or in a seemingly cellular-signal-resistant building. This is the problem that  Offline-First Application Design aims to solve.

The core idea behind Offline-First Application Design is to create apps that are still usable when an internet connection isn’t present. A user should be able to open your app and have an excellent experience, regardless of how many bars of service they have.

Something you’ll see mentioned often is that an offline state shouldn’t be treated as an error, but simply as another normal state of your application’s lifecycle. There are 101 ways to cleverly throw up warning flags or block a user from an operation that requires connectivity. The trick is to design your app to fail gracefully while the user is none-the-wiser about the turmoil going on underneath the hood. In short, it shouldn’t abruptly crash—or endlessly hang—when connectivity goes spotty or is lost completely.

So how can we mitigate these pitfalls and provide the user with a better experience? In order to solve this problem, you first need to understand what “offline” means in the context of your mobile application.

We’ve all had those times where our laptop, video game system, or smartphone was connected to our wired or wireless internet router, yet we couldn’t reach the website or service we were trying to access.This is where the idea of reachability comes into play.

There is first the question of “Can the device connect to a network?” Once we know the answer to that question, there is the follow-up question of “Can the device reach the internet?” With the answers to those two questions, your application will have the necessary insight to understand what it can or cannot do.

In our recent work on a sales tool app for iOS and Android, we were dealing with data that was not just being stored on the device, but was also being transmitted to a remote API server we built leveraging IBM’s LoopBack Framework. When records changed on the device, the server needed to get those changes. And conversely, when something changed on the server, the app needed to receive and react to those changes.

The scope of the problem was expanded by the fact that the app was a collaborative tool. When it can’t reach the server, it was critical for functions of the app to be turned off. One section of the app that managed objects we called conversations could not be created, edited, or deleted when an internet connection wasn’t present. This was because the app’s data fed a web-based service where those conversations could be shared with outside users (who don’t have the app). Because of this, the app listened for changes to the availability (the presence of a Wi-Fi or cellular signal) which could affect reachability (Can I contact the server?).

In other cases, the needs are much simpler. I recently began  a rewrite of an iOS app I published years ago that has a section used to display the most recent news articles. Everything presented in the app is read-only, and simply needs to be retrieved from the server and stored locally on the device for later display. The success of a request to the server isn’t make-or-break, but any time it fetches the local data, it at least attempts to ask the server for new data.

Here is an example of the code we use in our applications to keep an eye on network availability. This sample from the iOS version is written in Objective-C, and it uses the AFNetworking CocoaPod.

In our AppCoordinator class, we include this method:

- (void)configureNetworkAvailability {
    // Default to false
    self.availabilityStatusReachable = NO;
    
    // Network Activity for StrongLoop/Loopback indicator in the status bar.
    [[SLAFNetworkActivityIndicatorManager sharedManager] setEnabled:YES];
    
    // Network Activity indicator in the status bar.
    [[AFNetworkActivityIndicatorManager sharedManager] setEnabled:YES];

    AFNetworkReachabilityManager *networkReachabilityManager = [AFNetworkReachabilityManager sharedManager];
    
    [networkReachabilityManager setReachabilityStatusChangeBlock:^(AFNetworkReachabilityStatus status) {
        switch (status) {
            case AFNetworkReachabilityStatusReachableViaWWAN:
            case AFNetworkReachabilityStatusReachableViaWiFi:
                self.availabilityStatusReachable = YES;
                [[NSNotificationCenter defaultCenter] postNotificationName:kNotificationCenterAvailabilityStatusReachable object:nil userInfo:nil];
                break;
            case AFNetworkReachabilityStatusUnknown:
            case AFNetworkReachabilityStatusNotReachable:
                 self.availabilityStatusReachable = NO;
                 [[NSNotificationCenter defaultCenter] postNotificationName:kNotificationCenterAvailabilityStatusNotReachable object:nil userInfo:nil];
                break;
            default:
                break;
        }
    }];
    
    // Start monitoring network reachability.
    [networkReachabilityManager startMonitoring];
}

 

When we lose our connection to the network, a notification is pushed to our view controllers, which can update accordingly; when the connection comes back, another notification tells them  we’re back online. Here is a sample method in a ViewController:


 - (void)viewDidReceiveNotification:(NSNotification *)notification {
    [super viewDidReceiveNotification:notification];
        
    // The reachable property is simply a bool that can be checked throughout the view controller
    self.reachable = [[AppDelegateInstance appCoordinator] availabilityStatusReachable];
    
    if ([[notification name] isEqualToString:kNotificationCenterAvailabilityStatusReachable]) {
        // User is online; react accordingly
    }
    
    if ([[notification name] isEqualToString:kNotificationCenterAvailabilityStatusNotReachable]) {
        // User is offline; react accordingly

    }
    
    if ([[notification name] isEqualToString:kNotificationCenterApplicationDidPerformAuthenticatedUpdate]) {
        // User is online; react accordingly
    }
}

How you handle the presence or lack of an internet connection in your app is up to you. Is there data you want a user to see the moment they launch the app, regardless of their connectivity? Do you have operations that absolutely need to “phone home” like our aforementioned sales app? Or can the app run without necessarily needing any of the data provided by your API/servers right from the get-go? Some suggestions for how to handle reachability in your app could include:

  • Seed your app with content to view offline. One way to get around making the user wait when running the app for the first time is to seed the application’s data. By seeding, I mean including an initial set of evergreen data that will be preloaded into the database when the app is first launched, ensuring that there will be records available to display for any given screen. There are several methods for seeding data in an app utilizing Realm as its local storage solution. You could bundle a pre-populated database with your app, or you could  bundle data files in a specific format (i.e. JSON) and write the appropriate code to parse the files and import them into the database your app creates at first launch.
  • Disable features that won’t work offline; present messages to explain what’s going on. With our sales app, we took the approach of disabling/graying out buttons, disabling interaction on some controls, and when there was no other choice, presenting a message to the user saying they need to be online to perform the operation. This can be handled most easily by centralizing your data-retrieval methods into a helper or provider class, whose sole responsibility is being the gatekeeper for your application’s data. This would be a class that your views ask for data, but also handles making the requests to your remote service that keep the data fresh. It helps to reinforce the practice of “separation of concerns,” a tenet of popular application design patterns like MVC (Model-View-Controller) and MVMC (Model-ViewModel-Controller).
  • As a last resort, tell the user they can’t access the app and gracefully keep them out. That isn’t to say that there won’t be times when you are building an app that absolutely requires an internet connection to function. We encountered this with our work on ImagineCare, where interactivity with the clinicians and the server was critical in some sections, and showing older data could put the user at risk. In these types of situations, you could display a message to the user that they will need to seek an internet connection to resume, and block the UI preventing them from performing operations until the leveraged data is fresh.

 

By: David Beaudoin


Want to offer your customers a better experience?

Let's work together: Contact Us Today

Contributed by:

Newsletter Sign Up



Mad*Pow HQ

27 Congress Street
Portsmouth, NH 03801

Office: 603.436.7177
Fax: 603.386.6608

Boston

179 Lincoln Street
Boston, MA 02111

Office: 617.426.7177

PROJECT INQUIRIES

Complete Sale Inquiry Form

EMAIL US

Sales: solutions
Press: communications



Get in Touch

CALL US

603.387.8307

CONTACT US

SALES | PR/MARKETING | EVENTS