Storing Smartly

Our Mobile SDK features a great bit of technology called SmartStore, which allows for the secure offline storage of data.  SmartStore’s core goal is to fill the void for encrypted data with HTML5 development, but that doesn’t mean native code can’t access the same features.  Via HTML5, SmartStore is connected with a Cordova plugin – so when coding in Objective-C we just need to refer to to those native libraries directly.  For more on how the Mobile SDK’s native SmartStore code is constructed, checkout the documentation.

Adding the libraries

Unfortunately from a new project, the SmartStore libraries aren’t included with a native application.  To add them, simply grab them from the hybrid folder, add them to your project and then reference them in your code.  You can find the original libraries in the shared directory of the repository, or if you are looking within Finder:

And then depending where you add the libraries in Xcode, import the library header:

#import "SFNativeRestAppDelegate.h"
#import "../SmartStore/SFSmartStore.h"


Soup, Soup, Soup

If you are new to SmartStore’s structure, it might take a little explaining at first.  SmartStore uses a schema-less design which allows developers to have a flexible storage base without having to define specific columns to store a single row.  However, this means we also need to define what fields we want to be able to search against.  In SmartStore terms, we call this structure a soup (weird, I know – it is jargon leftover from when it was Newton technology – and we know how that platform was with words…).  Soups together form a store, which is how Objective-C will find a specific soup.

First we need to check for the existence of a soup, and register it if necessary.  The SFSmartStore class allows us to easily access the shared store structure globally.  To register a new soup, you first need to define the searchable fields you will use.  In this instance, I am adding the logic when the RootViewController is first instantiated so that I can be sure the soup will be available for later views.  Here, I am registering the Id and Name fields for later queries.

- (UIViewController*)newRootViewController {
    RootViewController *rootVC = [[RootViewController alloc] initWithNibName:@"RootViewController" bundle:nil];
    UINavigationController *navVC = [[UINavigationController alloc] initWithRootViewController:rootVC];
    [rootVC release];

    store = [SFSmartStore sharedStoreWithName:kDefaultSmartStoreName];
    BOOL exists = [store soupExists:@"Merchandise"];

    if(!exists) {
        NSMutableDictionary *columnIndex1 = [[NSMutableDictionary alloc] init];
        [columnIndex1 setObject:@"Id" forKey:@"path"];
        [columnIndex1 setObject:@"string" forKey:@"type"];

        NSMutableDictionary *columnIndex2 = [[NSMutableDictionary alloc] init];
        [columnIndex2 setObject:@"Name" forKey:@"path"];
        [columnIndex2 setObject:@"string" forKey:@"type"];

        [store registerSoup:@"Merchandise" withIndexSpecs:[NSArray arrayWithObjects:columnIndex1,columnIndex2, nil]];

        [columnIndex1 release];
        [columnIndex2 release];
    }
    exists = [store soupExists:@"Merchandise"];

    return navVC;
}

 

Storing and Searching Data

There is a structure in place, so now the data can be placed into SmartStore, indexed and searched against.  For instance, if you wanted to shove the results of  a REST API request into SmartStore – you just need to find the previously registered soup and upsert the records.  In this case, I’m adding the logic to response delegate of the SFRestRequest callout:

- (void)request:(SFRestRequest *)request didLoadResponse:(id)jsonResponse {
    NSArray *records = [jsonResponse objectForKey:@"records"];
    NSLog(@"request:didLoadResponse: #records: %d", records.count);
    self.dataRows = records;
    [self.tableView reloadData];

    //Cache in SmartStore in case of Offline
    NSError *error = nil;
    [store upsertEntries:records toSoup:@"Merchandise" withExternalIdPath:@"Id" error:&error];

}

 

By upserting them, you can reliably enter records in repeatedly and any record with the same ID will just be updated instead of creating a duplicate.  Now that we have the records in the system, we can search against the soup and retrieve them when needed.  In this example, I am attaching the logic to the text change event of a search bar, allowing me to use SmartStore as a quick (and secure) data cache for filtering down my previous results:

- (void)searchBar:(UISearchBar *)theSearchBar textDidChange:(NSString *)searchText {

    NSMutableDictionary *queryDictionary = [[ NSMutableDictionary alloc] init];
    NSString *searchString = [NSString stringWithFormat:@"%%%@%%", searchText];

    if([searchText length] > 0) {

        self.tableView.scrollEnabled = YES;

        [queryDictionary setObject:@"like" forKey:@"queryType"];
        [queryDictionary setObject:@"Name" forKey:@"indexPath"];
        [queryDictionary setObject:@"ascending" forKey:@"order"];
        [queryDictionary setObject:searchString forKey:@"likeKey"];
        [queryDictionary setObject:[NSNumber numberWithInt:500] forKey:@"pageSize"];

    } else {

        [queryDictionary setObject:@"range" forKey:@"queryType"];
        [queryDictionary setObject:@"Name" forKey:@"indexPath"];
        [queryDictionary setObject:@"ascending" forKey:@"order"];
        [queryDictionary setObject:[NSNumber numberWithInt:500] forKey:@"pageSize"];

    }

    SFSoupQuerySpec *querySpec = [[SFSoupQuerySpec alloc] initWithDictionary:queryDictionary];

    NSArray *results = [self.store querySoup:@"Merchandise" withQuerySpec:querySpec pageIndex:0];
    self.dataRows = results;
    [self.tableView reloadData];
    [queryDictionary release];
    [querySpec release];

}

Why SmartStore?

With all of the options for iOS, it is a valid question – particularly with some of the robust tools in Xcode around CoreData.  One reason would be parity with HTML5 related projects – but if you are already in the realm of native iOS development, that probably is not a concern.  I would say the more compelling reason is just the ease in which you can be assured you are utilizing an encrypted data store.  It is possible to encrypt CoreData as well, but not quite this straightforward.  Particularly if your use case is around caching API results and pulling them back for various uses without hitting the API again – I’d say SmartStore is worth a look.

Even More Info

I’ve got a sample application that utilizes the above (and a few other iOS tricks) over on my github account.  You can read more about using SmartStore with HTML5 from Sandeep’s earlier blog post.  Got questions?  Add them in the comment section below, or catch me on twitter.

tagged , , , , Bookmark the permalink. Trackbacks are closed, but you can post a comment.
  • http://twitter.com/joshbirk Josh Birk

    There’s also a video on my Dreamforce session up on youtube: http://www.youtube.com/watch?v=N_cTeuHRWRo

    For some reason, WordPress was choking on the link.

  • http://twitter.com/AdMedicus AdMedicus

    Is there a reference for composing queries with multiple operations. I need to query my “Contacts” soup for entries like some name that also have a certain OwnerId.