Externalizing the Data Source of Your iOS App

Mobile Development, Tutorials Comments (1)

In a previous blog post, I covered local connectedness. Briefly, local connectedness is directly sharing information, be it text based messages or files, with other devices in your vicinity (typically over a shared wi-fi connection). This model of sharing data between devices, though, is just one way of taking advantage of a mobile device’s connected nature. In this blog post, we will be exploring how to make your mLearning focused apps much more flexible, by taking advantage of their being constantly connected (or at least frequently connected) with the wider Internet.

A mobile app, especially an mLearning focused one, is frequently concerned with presenting information in some fashion. While the easiest, and most tempting from a development standpoint, option is to embed this information within the app (either directly within the app code, or within a file included with the distributed app), this can lead to post deployment problems. Primarily, any change in the presented information will require a new build of the app to be created, along with an associated update in the app store. While a well architected app can reduce the work required to make such a change, there are still inherent risks involved in this, due to the fact that users of an app can choose to not update an app, even when prompted (some of these concerns are alleviated with a MDM/OTA distribution system, but I’ll leave that topic to the Float team in the 6 P’s series of posts). Depending on the nature of the information presented in your app, this could lead to serious consequences. For example, if your mLearning app presented suggested drug dosages to medical personnel, outdated information in this context could lead to incorrect doses given to patients – a real, adverse consequence.

A much more flexible solution to this problem is to remove such information from the deployed application and instead have the app read the information from a readily available external source (typically a web service endpoint) at regular intervals. The app can then receive and parse the transmitted data, and present it within the interface exactly as it would with “packaged” data (i.e. Data built into the distributed app), in a much more immediate and seamless manner than the traditional app store update model. While there are drawbacks to this approach, such as the possibility of the app being offline at the time an information update becomes available or the additional latency inherent in having to contact the external source and receive the transmitted data, these can be minimized by providing a mechanism to cache the information within the app between updates, and ensuring the source is as “light” as possible (that is, minimize the amount of actual data that is needed to transmit the information “payload” of your app).

To illustrate this approach, we’ve put together a sample app that presents a collection of news story headlines in a table view, with the detail of an individual story being shown in a web view when the associated table row containing a headline is selected. In a more traditional app architecture, the headlines and stories to be presented in the app might have been hardcoded into an NSMutableArray and initialized in an appropriate method, such as viewDidLoad of the main ViewController, like so:

Declared in the ViewController header file:

NSMutableArray *newsHeadlines;
NSMutableArray *newsStories;

And initialized in viewDidLoad:

newsHeadlines =[[NSMutableArray alloc] initWithObjects:@"Headline 1",@"Headline 2",nil];
newsStories =[[NSMutableArray alloc] initWithObjects:@"Story 1 Text",@"Story 2 Text",nil];

With our in app data storage set up and initialized, it is then a matter of ensuring our ViewController is defined as a UITableViewDelegate and UITableViewDataSource, and implementing the relevant methods (cellForRowAtIndexPath and numberOfRowsInSection) to present the data contained in the newsHeadlines variable in the UITableView. We will implement didSelectRowAtIndexPath to create a ViewController containing a UIWebView (initialized to the string in the element from the newsStories variable that corresponds to the element that triggered the row selection) and push the created ViewController onto the main NavigationController to present the associated story in the app interface.

As mentioned previously, this approach will work in that the app data will be presented as intended within the app and function appropriately. The drawback, however, will come when we need to deploy a new set of headlines and associated stories to our app audience. This will require a modification of the app code base (changing the two lines of code that initialize our newsHeadlines and newsStories variables), then a rebuild and redeployment of the app. While in the example this represents a minimal amount of effort, for a more complicated app this could potentially represent several hours, if not days, of additional development effort.

Now, lets look at how we can restructure our app to instead use an external data source to populate our app data structures, and so make our app that much more flexible. For demonstration purposes, we are going to use a readily available webservice, Google News, as our data source of mLearning focused news stories. For most apps, the data source will most likely need to be custom developed, but the concepts remain largely the same.

With our data source identified, we now need to determine how we can utilize the service to get the necessary data into our app. At a high level, this is simply a matter of sending a request to the necessary endpoint on app startup (ie, in a method such as viewDidLoad), then parsing the returned data to derive our needed information. For the purposes of sending a request we can utilize the NSData class and in particular the message dataWithContentsOfURL – this message sends a request to a given URL and returns the response as an NSData object. The code to retrieve our google news “stream” then becomes:

NSData *data =[NSData dataWithContentsOfURL:[NSURL URLWithString:@"https://ajax.googleapis.com/ajax/services/search/news?v=1.0&q=mobile%20learning"]];

Note how our query of mobile learning (to retrieve mLearning focused stories from google news) is embedded into the URL itself. With this neatly encapsulated message, the iOS SDK takes care of sending the request to the given URL and receiving the response into the supplied variable. With the request completed, we now need to focus on how to extract our needed information from the returned response.

Looking at the documentation provided by Google, we see both the structure of the returned data, and the fact that is returned in JSON format. While there is no native JSON parser in the iOS SDK, there is the excellent TouchJSON library readily available on github, that will do the work of decoding the JSON string returned by google news (contained in our data variable) and converting it into a NSDictionary with a structure corresponding to that given in the google documentation. The code to do this is as follows:

NSDictionary *result = [[CJSONDeserializer deserializer] deserializeAsDictionary:data error:&error];

With the JSON data now parsed and decoded into a manageable format we can now further process our result variable to populate the newsHeadlines and newsStories variable with google supplied data, like so:

// Set up a temporary variable to hold the main results portion from the JSON object
NSMutableArray *tmpArray = [[result objectForKey:@"responseData"] objectForKey:@"results"];

newsHeadlines = [[NSMutableArray alloc] init];
newsStories = [[NSMutableArray alloc] init];
for (id nextNews in tmpArray) {
[newsHeadlines addObject:(NSString*)[nextNews objectForKey:@"titleNoFormatting"]];
[newsStories addObject:(NSString*)[nextNews objectForKey:@"content"]];
}

With the newsHeadlines and newsStories variables now populated with the appropriate data, our app will now function exactly the same as before, but with data derived from an independently updated source, thus negating the need for a frequent, and manual, app update process based on changing content.

As mentioned before, one drawback to this approach is the possibility that the app is started when not connected to the internet, and thus disconnected from the external data source. While we can check for this condition and present appropriate alerts to the user when this is the case, the app can be made more usable by caching the externally supplied data in a persistent local data store and presenting this cached data in the event that the user starts the app when not connected to the internet.

For the purposes of this demonstration we will use the app plist to store this cached data, but for more complex data structures, an SQLite database or Core Data may be more appropriate. Our first task in this regard is to store a set of data retrieved from google in the plist. This essentially entails resolving the path of the plist file on the device (done by combining the values of several app related variables together), formatting the data we want to write to it, and then writing this formatted data to it.

// determine the root path of the app and location of the Data.plist file within
NSString *rootPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
NSString *plistPath = [rootPath stringByAppendingPathComponent:@"Data.plist"];

// create a keyed dictionary containing our news data
NSDictionary *plistDict = [NSDictionary dictionaryWithObjects:[NSArray arrayWithObjects: newsHeadlines, newsStories, nil] forKeys:[NSArray arrayWithObjects: @"newsHeadlines", @”newsStories”, nil]];
// serialize our dictionary into a property list
NSData *plistData = [NSPropertyListSerialization dataFromPropertyList:plistDict format:NSPropertyListXMLFormat_v1_0
errorDescription:&error];

if(plistData) {
// if successful, write the serialized data to the plist file
[plistData writeToFile:plistPath atomically:YES];
}

This should be done immediately after retrieving the latest set of data from the google data source. With the data now stored in a persistent local data store, we now have our “fallback” cached copy of data, to be used when an internet connection is not available. In short, if no data is returned from the request to the Google URL, we should then read the data from the plist store and present that in the app (perhaps with an indicator in the interface that “stale” data is being displayed). We can retrieve the stored data from the plist with the following:

// determine the path of the plist file on the device
NSPropertyListFormat format;
NSString *rootPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,NSUserDomainMask, YES) objectAtIndex:0];
NSString *plistPath = [rootPath stringByAppendingPathComponent:@"Data.plist"];

// open the plist file for reading
if (![[NSFileManager defaultManager] fileExistsAtPath:plistPath]) {
plistPath = [[NSBundle mainBundle] pathForResource:@"Data" ofType:@"plist"];
}

// decode the contents of the plist file into an NSData variable for processing
NSData *plistXML = [[NSFileManager defaultManager] contentsAtPath:plistPath];
// create a keyed dictionary structure containing the plist contents
NSDictionary *temp = (NSDictionary *)[NSPropertyListSerialization propertyListFromData:plistXML mutabilityOption:NSPropertyListMutableContainersAndLeaves format:&format errorDescription:&errorDesc];

if (temp) {
// Found cached items, put them into our local storage variables
NSMutableArray *th = (NSMutableArray*)[temp objectForKey:@"newsHeadlines"];
NSMutableArray *ts = (NSMutableArray*)[temp objectForKey:@"newsStories"];
newsHeadlines = [[NSMutableArray alloc] init];
newsStories = [[NSMutableArray alloc] init];
for (id nextHeadline in th) [newsHeadlines addObject:(NSString*)nextHeadline];
for (id nextStory in ts) [newsStories addObject:(NSString*)nextStory];
}

With this simple demo, we hope we have illustrated the flexibility that you can give your content-focused mLearning apps in regards to presenting the most up to date content within your app in a way that is unobtrusive to the end user, while still maintaining a high degree of robustness in the event of intermittent connection. Using this approach can greatly increase the timeliness of the information presented by your app and, as a result, the usefulness of your app to your end-users.

Contact us to learn more about how your next mobile learning project can leverage this feature.

Follow Float
The following two tabs change content below.

Mark Tovey

Mark is a Mobile Developer at Float with a B.S. in Applied Science (Information Science). Mark has a varied background in the I.T. industry, having worked as both a mainframe programmer/analyst and web developer over the course of his career, in areas as diverse as homeland security, education, insurance, e-commerce, and e-learning. This breadth of experience has given him a deep understanding of all of the phases of the software development life cycle. In the recent past, Mark has developed many content managed websites using open source software, including the Oyez Project, as well as developing interactives for clients such as The Adler Planetarium and Shure Inc. Mark is also an instructor at Bradley University, teaching an introductory course to the Javascript and Actionscript scripting languages.

» Mobile Development, Tutorials » Externalizing the Data Source of...
On June 2, 2011
By
, ,

One Response to Externalizing the Data Source of Your iOS App

  1. Sascha says:

    We have been building applications for many customers ourselves and used this approach. The question always was, where is the data coming from?

    We didn’t find a good solution for this, so we create a solution ourselves, a cloud-based CMS for mobile applications called StorageRoom. Editors can easily manage content in a cloud-based interface and developers load or synchronize the content with the JSON API.

    Please give it a try, it might just be what you need.

Leave a Reply

Your email address will not be published. Required fields are marked *

« »