Skip to Content

The OData API for scheduling requests on the <SODataStoreAsync> protocol is common for both online and offline stores, which means that you don’t need to modify your request/response interface for using either mode.  However, there are two important distinctions in the internal behavior of the stores that you should be aware of.  These should guide your decision to use one, the other, or both.

Distinctions between SODataOnlineStore and SODataOfflineStore behavior

  1. The SODataOfflineStore is only populated by it’s defining requests.  Requests outside their scope return no records

    The SODataOfflineStore store takes a dictionary of “defining requests” when it is initialized for the first time, which are used by the MobiLink service to fetch data from the back end, and load to the client database.

    All requests made against the store are then read/writes to the client database.  So, if a request attempts to read a resource from a filter outside the scope of the defining requests, no entities will be returned.

    For example, if the defining request = "TravelagencyCollection", a request for /TravelagencyCollection, or /TravelagencyCollection?$filter=city eq 'Rome' will return all travel agencies, and all travel agencies in Rome, respectively.

    However, if the defining request = "TravelagencyCollection?$filter=country eq 'US'", a request for /TravelagencyCollection?$filter=city eq 'Beijing' will return no entities, even if travel agencies in Beijing exist in the back end system.  Likewise, a request to a separate collection for which there is no defining request, e.g. /FlightCollection?$filter=fldate gt datetime'2013-09-16T00:00' will return no records.

    SODataOnlineStore sends all requests to the back end, and does not have defining requests.

  2. OData Function Imports are not supported by SODataOfflineStore

    Requests to the the SODataOfflineStore are filtered by the
    $metadata document, and requests which are not related to a collection are rejected with an error.  So, Function Imports, even if they reference collections which are in the scope of the defining requests, are not allowed on the SODataOfflineStore.

    Function Imports are supported by the
    SODataOnlineStore.


Using both SODataOnlineStore and SODataOfflineStore

There are certainly use cases for pure ‘online’ apps, which make every request to the back end.  In this case, just use SODataOnlineStore.

There are also a few use cases for pure ‘offline’ apps, (though many fewer, I think), where the use case is highly transactional, but the relevant data is expected to be fixed during the usage (no live data is expected from the back end).  For example, if the application is designed to enter customer orders, and all the product data is constant, a defining request can be added for the product info, and it will always be read locally, while the customer orders are written and edited locally.

However, if the application has some data which should be offline-enabled (like customer orders), but you also need access to real-time pricing or inventory information on products, then you should use SODataOnlineStore and SODataOfflineStore together, for a ‘mixed mode’.

An example implementation

I always work with a central DataController singleton in my apps, which handles the configuration of the online and offline stores, and the request/response delegates.  That way, I can write a -fetch: request, without needing to worry about where the request is going.

I accomplish this by creating both a localStore (offline store) and networkStore (online store) on the DataController.  The defining requests for the offline store are set by the developer, from within AppDelegate when the DataController is initialized.  Then, all my CRUD requests in the app invoke my own  -scheduleRequestForResource:withMode:withEntity:withCompletion method, which wraps the standard –scheduleRequest:delegate: method in the API. 


You don’t necessarily need to wrap the standard API, but I like to implement my own method that adds a block callback, lets me add some internal magic to check if the store is open, and in this case, select which store (local, or network) should be used. 

Let’s take a look at how this can be done.  GIST link: StoreForRequestResourcePath


-(id<ODataStore>)storeForRequestToResourcePath:(NSString *)resourcePath

    /*

    First, test if mode is online- or offline-only anyway.

    */

    if (self.workingMode == WorkingModeOnline) {

 

        return self.networkStore;

     

    } else if (self.workingMode == WorkingModeOffline) {

 

        return self.localStore;

    }

 

    /*

    And if there are any defining requests to test anyway

    */

    if (self.definingRequests.count < 1) {

        return self.networkStore;

    }

 

    /*

    Second, do a compare to see if the collection of the request matches the collection of the defining requests.  The principle here is that you can offline a collection, or filter of a collection, and all requests will be executed against the db for that collection.  You should adjust the filters of your defining requests to support the expected scope of requests for the user.

    */

    __block NSString * (^collectionName)(NSString *) = ^NSString * (NSString *string){

EDIT Jan 8, 2014:  handling for fully-qualified URLs in resource path, and for entity id’s   

      

        NSString *relativeString = string;

        /*

         If the string is a fully-qualified URL, parse out just the relative resource path

         */

        if ([[relativeString substringToIndex:4] isEqualToString:@”http”]) {

          

            NSURL *url = [NSURL URLWithString:relativeString];

            relativeString = [url lastPathComponent];

        }

      

        /*

         Trim parenthesis element from last path component

         */

        if ([relativeString rangeOfString:@”(“].location != NSNotFound) {

            relativeString = [relativeString substringToIndex:[relativeString rangeOfString:@”(“].location];

        }

  

        return [relativeString rangeOfString:@”?”].location != NSNotFound ? [relativeString substringToIndex:[relativeString rangeOfString:@”?”].location] : relativeString;

    };

 

    NSString *resourcePathCollectionName = collectionName(resourcePath);

 

    for (NSString *request in self.definingRequests) {

     

        NSString *definingRequestCollectionName = collectionName(request);

     

        if ((resourcePathCollectionName && definingRequestCollectionName) && [resourcePathCollectionName isEqualToString:definingRequestCollectionName]) {

            return self.localStore;

        }

    }

 

    /*

    Last, the default will always be to fall back to the network store (online request). This should cover Function Imports, and any requests which are not within the scope of the defining request collections

    */

    return self.networkStore;

}


First, I do a check of the workingMode, which is an enum I added to control which store(s) should be active.  The default value in my framework is WorkingModeMixed, which means that both Online and Offline stores are used in the app.  But, if only one is available, that one is returned.  (In this framework, the developer should set an alternate working mode WorkingModeOnline or WorkingModeOffline from the AppDelegate -applicationDidBecomeActive:, if desired).  Also, if the app is in Mixed mode, but there are no defining requests, then the request should go over the network.

If the app is in Mixed mode, and there are defining requests, then I check to see if the collection of the incoming request matches that of one of the defining requests.  If so, then it should read from the local database.  If not, or if the request happens to be a Function Import, then the comparison will fail, and the online store is selected.

Summary

In short, there are many cases in which it makes sense to have both an online and offline store active in your app.  You can control which requests go to which store by calling each request on a specific store, which would be the standard use of the -scheduleRequest:delegate: API.  But, if you have wrapped the standard API, you could pick the correct store dynamically, using the offline store’s defining requests as the filter.

To report this post you need to login first.

1 Comment

You must be Logged on to comment or reply to a post.

  1. Marvin Hoffmann

    Excellent post.

    I think we have way to less best practice blog posts or documents. Most of the information published in SCN are how-to guides or tutorials, which are very good for starting, but after some deeper digging into a topic you would like to get some best practices or some explanations how things are working on a lower level.

    I have to say that I am not developing in iOS, but I am involved in several Android projects. Here I was anyway using a Singleton ConnectionManager class which wraps some needed functionality, but till now I separated my online and offline request methods (but not the request itself). I like the idea to have only one request method, so that the Data Controller can decide by its own if this should be an offline or online request.

    Thanks for sharing this!

    Regards

    Marvin

    (0) 

Leave a Reply