Introduction

Recently I was at CocoaConf (http://cocoaconf.com) in DC, and attended an interesting session titled "Strategies, Tips, and Tricks for Sometimes Connected Apps" by Ken Auer (twitter handle @kauerrolemodel). The content of this talk resonated with some thoughts I had on implementing an Occasionally Connected Application. This blog post will discuss technical requirements for such an app, and some implementation strategies to meet those requirements. To implement the example app, I will use the Parse SDK (http://www.parse.com), an easy to use web-service framework that connects to the Parse backend.

The Occasionally Connected Application

Consider a simple "To-do List" application that is used predominately as an offline app that does not require a network connection. However, if the user does register for an account, this app allows the user to synchronize tasks with a web service. Once the application synchronizes with the cloud, it can be viewed and modified via desktop computers on the web site, or used by an application on a different platform; an Android port of your iOS application for example.

This is a good example of an "Occasionally Connected Application" with the following features:

  1. The application has features that are primarily used on device without need for a network connection.
  2. The application also has features in the cloud, such as data sharing with a web service, which will become available if the user has registered for an account.

From this we can determine the following requirements:

  1. The application will allow use even if device is not connected to the network.
  2. A registered user can save and retrieve data from the web service.
  3. If a user manipulates data while the device is not connected, the application will synchronize this data with the web service at a later time.
  4. A user can run the application, without creating an account, by being a guest user.
  5. A guest user can become a registered user, by registering for an account.

In the rest of this post, I will discuss possible designs to meet the above requirements for an Occasionally Connected Application.

The application will allow use even if device is not connected to the network.

There are multiple ways to allow an application that is normally synced with a web service to be used locally without a network connection. One way is to create a local storage model on device that can be periodically synchronized with the database on the web.

This data model can vary in complexity depending on the amount of data to be stored. For instance, a Property List file is sufficient to store a few dozen entries for a to-do list, but a SQLite database (http://www.sqlite.org) may be needed to store several hundred entries for an extremely organized person.

A registered user can save and retrieve data from the web portal.

For any application that requires saving and retrieving data from the web, we will need to implement web-service calls on the application. You can easily implement network requests using the NSURLConnection protocol provided by Apple's frameworks, but this implementation does not take into consideration environments with intermittent connectivity. One possible way would be to implement network requests using the Command Dispatch Pattern. Please read Jack Cox and Nathan Jones's blog post on this subject, titled "Managing Complex Mobile Transactions", for more information. (https://www.captechconsulting.com/blogs/managing-complex-mobile-transactions) The command dispatch pattern uses a NSOperationQueue to store NSOperation objects that encapsulate web service calls as commands. Operations within the queue are ordered according to priority levels and inter-operation object dependencies and are executed accordingly. The command dispatch pattern adds notifications that are sent for command completions and failures.

Parse SDK save and saveInBackgroundWithBlock

To store our data into the Parse cloud service, we'll need to convert our data model into the Parse.PFObject, which has class methods for saving to the Parse web service. In this example code, I will directly create a PFObject instead.

To save the PFObject to the Parse web service, we can save it synchronously using,

[todoItem save];

We can also save the PFObject in the background using blocks,

[todoItem saveInBackgroundWithBlock:^(BOOL succeeded, NSError *error) {
 if (!error) {
 // The todoItem saved successfully.
 } else {
 // There was an error saving the todoItem.
 }
}];

If a user manipulates data while the device is not connected, the application will synchronize this data with the web portal at a later time.

In instances where availability of the data and the application is a higher priority than real-time accuracy of the data, we can allow the user to manipulate the local data and queue the synchronization of the data for later. I will give two examples of how to implement this.

Command dispatch pattern

One way to implement using the command dispatch pattern, is to setup a second command queue, the Delayed Dispatch Queue (DDQ), which commands will be copied to if the network is unreachable, or the web service is down. Once the network becomes available again, the queue will receive a notification and start executing commands. If there is a need to re-establish the delayed operations when the app is closed and reopened, you will need to persist the DDQ, perhaps by writing it to disk. On application launch, when the network connection is established, the DDQ will execute any remaining operations in the background.

Parse SDK saveEventually

The Parse SDK has an easy to use method, saveEventually, for deferred synchronization of data. This technique works if the application does not need to know when the save has completed. If the user does not currently have a network connection, saveEventually will store the update on the device until a network connection is re-established. If the app is closed before the connection is established, Parse will try again the next time the app is opened.

PFObject *todoItem = [PFObject objectWithClassName:@"TodoItem"];
[todoItem setObject:@"Buy 100 tons of cat litter" forKey:@"Todo"];
 
//check if there is network
if (isNetworkAvailable)
{
 //store data now in background
 [todoItem saveInBackground];
}
else //no network available
{
 //save this object using Parse API, in the background, eventually. 	
 [todoItem saveEventually];
}

A user can run the application, without creating an account, by being a guest user.

For simple applications, where upon first launch there is no apparent benefit to creating an account, it may be best to allow the user to start using the app right away without registering.

In our "Todo List" app example, on launch the app will check to see if a cached user is available on disk. If the cached user is available, the app will load it. If not, the app will create a new anonymous user account and load it as the current user. This anonymous account has the same abilities as a registered account; the only difference is that the user does not have access to the username or password associated with the account.

With Parse, this is how you determine if a cached user exists:

PFUser *currentUser = [PFUser currentUser];
if (currentUser) {
 // do stuff with the user
} else {
 // create an anonymous user
}

It is also very simple using Parse to login and simultaneously create an anonymous user:


[PFAnonymousUtils logInWithBlock:^(PFUser *user, NSError *error) {
if (error) {
NSLog(@"Anonymous login failed.");
} else {
NSLog(@"Anonymous user logged in.");
}
}];

Anonymous users can also be automatically created without requiring a network connection, so that the user can begin using the application immediately without waiting for a network request. On application launch, remember to run the [PFUser enableAutomaticUser] class method to enable automatic creation of anonymous users.

A guest user can become a registered user, by registering for an account.

Let assume the user wants to create an account and enable premium features of the application. When an anonymous user taps on a button for "synchronize with cloud" the application will prompt the user to create an account. Using the Parse SDK, you can convert an anonymous user into a registered user by setting the username and password, then calling signUp.

First, we check if the current user is anonymous,

if ([PFAnonymousUtils isLinkedWithUser:[PFUser currentUser]]) {
 //yes, it's an anonymous user. Display sign up page.
} else {
 //no it's a registered user.
}
/objc>
 

If the user decides to sign up for an account, they can submit the sign up form with username, email, and password.  The app will login with those credentials, to convert the anonymous user to a registered user.  The new user will retain all of its data from the anonymous account./p> ="objc"> PFUser *user = [PFUser currentUser]; user.username = @"my name"; user.password = @"my pass"; user.email = @"email@example.com"; [user signUpInBackgroundWithBlock:^(BOOL succeeded, NSError *error) { if (!error) { // user is logged in } else { // error with login. } }];

Conclusion

In this blog post, I have introduced a simple Todo List app that has many features required of an "Occasionally Connected Application". Using the Parse SDK, I've also shown a straightforward and easy way to implement these requirements. Hopefully, some of these tips will keep your apps synchronized in today's not-always-connected world.