Thursday, January 31, 2013

AeroGear iOS lib - Milestone 3

Today we are happy to announce the immediate availability of Milestone 3 of the AeroGear iOS library. Focus on this release was API overhaul cleanups as we prepare for the final release, but that didn't stop us to provide two neat new features that we hope our users will find them useful.

Pagination


The first feature is Pagination support of the result set returned by the server. You now have the ability to scroll either backwards or forward in the result set with the API trying to be flexible enough to support different pagination strategies supported by a server. Paging metadata located in the server response (either in the header or in the body) are used to identify the next or the previous result set. For example, in Twitter case, paging metadata are located in the body of the response, using next_page or previous_page JSON keys at the root level to identify the next or previous result set. The location of this metadata as well as naming, is fully configurable during the creation of the Pipe, thus enabling greater flexibility in supporting several different paging strategies. By default, if no configuration is applied, the library will choose to use Web Linking as it's paging strategy.

Here is an example of pagination that goes against the AeroGear Controller Server:

    NSURL* baseURL = [NSURL URLWithString:@"https://controller-aerogear.rhcloud.com/aerogear-controller-demo"];
    AGPipeline* pipeline = [AGPipeline pipelineWithBaseURL:baseURL];
    
    id<AGPipe> cars = [pipeline pipe:^(id<AGPipeConfig> config) {
        [config setName:@"cars-custom"];
        [config setNextIdentifier:@"AG-Links-Next"];
        [config setPreviousIdentifier:@"AG-Links-Previous"];
        [config setMetadataLocation:@"header"];
        // --optional config option--
        // you have the option to set default parameters that
        // will be passed along during pagination kickstart.
        // If not specified, a default query parameter provider
        // will be installed, that uses the names "offset" and
        // "limit" (two popular names found in pagination mechanisms)
        // initialized to 0 (first page) and 10 (ten items per page) respectively.
        [config setParameterProvider:@{@"color" : @"black", @"page" : @"2", @"per_page" : @10}];        
    }];     
First we create our pipeline. Notice that in the Pipe configuration object, we explicitely declare the name of the paging identifiers supported by the server, as well as the the location of these identifiers in the response. Here we used header to specify that the paging identifiers are to be found in the headers of the response. We can also specify body if the identifiers are to be found in the body of the response (like with Twitter's search), or webLinking (default) if your server follows the Web Linking standard.

To kick-start pagination, you use the method readWithParams of the underlying AGPipe, passing your desired query parameters to the server. You have the option not to specify query parameters (by passing nil), which in that case the pipe will use the default parameter provider set during the pipe creation (with the setParameterProvider config option). Upon successfully completion, the pagedResultSet (an enhanced category of NSMutableArray) will allow you to scroll through the result set:

 __block NSMutableArray *pagedResultSet;
 // fetch the first page
 [cars readWithParams:@{@"color" : @"black", @"offset" : @"0", @"limit" : @1} success:^(id responseObject) {
     pagedResultSet = responseObject;

     // do something

 } failure:^(NSError *error) {
     //handle error
 }];

To move forward or backwards in the result set, you simple call next or previous on the pagedResultSet, passing on the familiar callbacks to handle the response:

 
    // go forward..
    [pagedResultSet next:^(id responseObject) {
        // do something

   // go backwards..
   [pagedResultSet previous:^(id responseObject) {
         // do something
         
     } failure:^(NSError *error) {
         // handle error
   }];
    } failure:^(NSError *error) {
        // handle error
    }];

Note that exception cases like moving beyond last or first page is left on the behaviour of the specific server implementation, that is the library will not try treat it differently. Some servers can throw an error (like Twitter or AeroGear Controller does) by responding with an http error response, or simply return an empty list. The user is responsible to cater for exception cases like this.

We suggest you have a look here and here, two examples that go against the two most popular servers Twitter and Github. Further, if you want a feel on how paging is implemented on the other platforms we support (Android and JS) here is a page that collectively describes the API under those platforms.


Property List Storage


Drived by the need to support long term storage of objects and as an exercise of the AGStore mechanism, a simple Property List based extension was implemented. Familiar methods of reading, saving and removing objects are provided, this time though persisting the changes on the filesystem. Here is a quick example, that stores an One Time Password (OTP) secret in the filesystem so that OTP tokens can be generated later (check AeroGear OTP iOS library and demo for more information on OTP).

    AGDataManager* manager = [AGDataManager manager];
    id<AGStore> plistStore = [manager store:^(id<AGStoreConfig> config) {
       [config setName:@"secrets"];
       [config setType:@"PLIST"];
    }];

Here we initialize the store using a PLIST as type to identify that we want a property list storage and giving up a name that will be also used as the filename.

Now we can simple call the familiar methods to read and save objects in the store:

    // the object to save (e.g. a dictionary)
    NSDictionary *saveOtp = [NSDictionary dictionaryWithObjectsAndKeys:@"19a01df0281afcdbe", @"otp", @"1", @"id", nil];

    // save it
    [plistStore save:saveOtp success:^(id object) {
        // the object has been saved
    } failure:^(NSError *error) {
        //handle error
    }];

    // read it back (note that we pass the ID)
    [plistStore read:@"1" success:^(id object) {
        id readOtp = object;
    } failure:^(NSError *error) {
        //handle error
    }];  

So go ahead and give them a try. We will be happy to hear your thoughts, suggestions and ways to improve it better. Join our mailing list, hangout on our #aerogear IRC channel and better, file us bugs!

Enjoy!