As part of testing our Mobile SDK 2.0, I decided I would write “Share Wonder”. The basic use case is simple. People take pictures of great things they want to share with their co-workers who can then comment on them. Share Wonder is open source and available on my Github Repo.

Salesforce Platform Mobile SDK 2.0

Architecturally, Share Wonder is a hybrid local app that also requires a Salesforce object to be installed using a package. The user interface is powered by HTML5, CSS and Javascript. There’s quite a bit of Javascript in the app. Backbone.js powers the Smart Object framework, which gives Share Wonder it’s offline capabilities, and jQuery powers the calls to the server. The app itself is constructed using AngularJS.

On the Salesforce Platform side, there’s a custom object.  Images are uploaded as attachments on the custom object.  Comments are posted to the record’s Chatter feed.  I’ve included some comments below about architectural alternatives.

For more details on how the Mobile SDK is built, check out our Salesforce Platform Mobile Services site.

Start Up

A key improvement in the Salesforce Mobile SDK 2.0 is the addition of “bootconfig.json”. For Share Wonder, I wanted to first experience to be something other than a splash screen, and I wanted to control when the app authenticated with Salesforce. The file looks like this:

{
    "remoteAccessConsumerKey": "3MVG9y6x0357Hlees6cF_JBqNJre1T9Vju9if.xcrPFgxi.9S3LnhvsEbQ3ao3Hg5oc3sfyQxyHEHmPGViQK_",
    "oauthRedirectURI": "sfdcreid:///mobilesdk/detect/oauth/done",
    "oauthScopes": ["full", "refresh_token"],
    "isLocal": true,
    "startPage": "home.html",
    "errorPage": "error.html",
    "shouldAuthenticate": false,
    "attemptOfflineLoad": false
}

Note that “startPage” is set to “home.html”. This file is outside of the general AngularJS app structure and is just a quick screen talking about the app. The other variable I changed is “shouldAuthenticate”. If this is set to “true”, the SDK will attempt to authenticate immediately. Setting it to “false” lets me control that more carefully.

Once the user has seen the start page, I want the system to go through the authentication dance. With a quick modification to the standard index.html the forceios CLI gives you, you can easily control that flow. Note the call to “authenticate” instead of “getAuthCredentials”.

document.addEventListener("deviceready", function() {
    cordova.require("salesforce/plugin/oauth").authenticate(
    	//...
    );
});

It’s important to note that the SDK gives you a great deal of functionality out of the box. This includes complete OAuth 2.0 management and refresh token handling.  Also, and this is a great bonus, every iOS app you create with the SDK automatically includes a “Settings” section that let’s you control login endpoint (product, sandbox, other) and also lets you force a logout.

AngularJS Integration

If you haven’t worked much with AngularJS, I’d encourage you to give it a look. It has powerful features that make it easy to build modern web apps. Checkout my earlier article if you’re interested, and if you’d like to see the simplest possible AngularJS app, I recommend Sangular, the app I wrote to teach myself a variety of AngularJS concepts like Modules, Factories and Directives.

Although the traditional use of “index.html” isn’t required for either AngularJS or hybrid local apps, I’ve stuck to that convention for convenience. Index marshals the various Javascript libraries the app requires, and contains the call to authenticate. The full version of the call to authenticate includes both a success and error callback. Success calls “bootstrapAngularApp” and sends the user’s authentication credentials.

document.addEventListener("deviceready", function() {
    cordova.require("salesforce/plugin/oauth").authenticate(
    	function(creds) {
        	bootstrapAngularApp(creds);
     	}, 
     	function(error) {
        	console.log("Auth failed: " + error);
     	}
    );
});

“BootstrapAngularApp” handles the wiring between Salesforce, AngularForce and the rest of the app. Once AngularJS has the credentials, all you have to do is tell it about the objects it should capture.  (Thank to Raja Rao DV for kicking off the AngularJS work and bootstrapAngularApp pattern!)

Smart Objects

The Mobile SDK 2.0 includes new Smart Object functionality. The good news about Smart Objects in AngularJS is that you don’t have to do anything special to use them. Simply instantiate and object using AngularForceObjectFactory, and you are already tied in to the Smart Object framework. Share Wonder connects to the custom object “Wonder__c” using the following:

angular.module('Wonder__c', []).factory('Wonder__c', function (AngularForceObjectFactory) {
    //Describe the contact object
    var objDesc = {
        type: 'Wonder__c',
        fields: ['Id', 'Name', 'Active__c', 'Description__c', 'Primary_Image__c'],
        where: 'Active__c = TRUE',
        orderBy: 'CreatedDate DESC',
        limit: 20
    };
    var Wonder__c = AngularForceObjectFactory(objDesc);

    return Wonder__c;
});

Smart Objects are great because you, the developer, don’t have to worry about managing the online or offline status of the app. The Smart Object framework does that for you. You can see this in action by cloning the project in Github and running it in your emulator. Note that in Share Wonder, I’ve taken a bit of a short cut with the images and those only show when you are online. More to come on that front in the not too distant future.

Apex REST

Apex REST is one of my favorite features of the Salesforce platform, and I used it in Share Wonder in two ways. Note that, in order to use it with the AngularForce library as is, I had to make a couple of small changes that let me expose the functionality.  You can see these around line 97 and 98 and around lines 295 and 296.

Apex REST use is pretty straightforward.  First, when I have a picture that I want to share, I use Apex REST to send both the pictures and the data about the picture to Salesforce at the same time. The first thing I do is grab the Base64 representation of the new picture, along with whatever comment the user made, and combine those together into a JSON representation. Next, I pass this into the special “apexrest” method I created in the AngularForceObjectFactory.

data = {
    "image" : $scope.newPicture,
    "description" : $scope.description,
};

Wonder__c.apexrest(
    "/wonderHelper",
    function(data) {
        logToConsole("combined success");
        $scope.doList();
    },
    function(data) {
    	//failure
    },
    "post",
    data,
    {}, 
    false);

The Apex mapped to “/wonderHelper” takes over from there.

The second place I used Apex REST is when loading the conversations around an object. The Javascript method is pretty similar:

$scope.doDisplayDetail = function() {
    Wonder__c.apexrest(
        "/wonder/detail/"+$scope.targetId,
        function(data) {
            logToConsole("wonder detail success");
            $scope.summary = data;
            $scope.processing = false;
            $scope.$apply();
        },
        function(data) {
            logToConsole("combined failure");
            logArray(data);
            $scope.errorText = data.responseText;
            $scope.processing = false;
            $scope.$apply();
        },
        "get",
        {},
        {}, 
        false);

However, the Apex method gave me an excuse to try out another new feature: the ConnectAPI in Apex. The ConnectAPI gives you easy access to Chatter data. The ConnectAPI in Apex gives you this same ease of development, but eliminates the need to use the dedicated ConnectAPI REST API. This is far and away the easiest way to get Chatter related to a particular record. Notice that I’ve created a custom data structure, WonderDetail, to contain the data I’m returning to the client. Apex REST handles the magic behind serialization.

@HttpGet
global static WonderDetail getDetail() {
    RestRequest req = RestContext.request;
    String wonderId = req.requestURI.substring(req.requestURI.lastIndexOf('/')+1);
    Wonder__c wonder = [Select Id, Name, Description__c, Active__c, Primary_Image__c,
                        CreatedDate, OwnerId, Owner.Name, CreatedById, 
                        CreatedBy.Name, CreatedBy.SmallPhotoUrl 
                        From Wonder__c where Id = :wonderId];

    List feed = ConnectApi.ChatterFeeds.getFeedItemsFromFeed(null, ConnectApi.FeedType.Record, wonder.id).items;
	WonderDetail ret = new WonderDetail();
    ret.wonder = wonder;
    ret.feed = feed;
	return ret;            
}

You can learn more about the role of Apex REST in mobile apps by reading Quinton Wall’s excellent article, Enterprise Mobile App Patterns.

Architectural Alternatives

I could have built this app in a few different ways. For example, rather than attaching the picture to a custom object record, I could have simply posted it into a Chatter Group. The upside to doing that would be that the admin wouldn’t have to install a package into the org. In fact, you wouldn’t need a separate app for that, it could simply be something you use as part of the Salesforce Chatter app. The upside of requiring an AppExchange install, on the other hand, is that it’s easier for you, the app developer, to monetize. Finally, if I had used the Chatter architecture, it would have been harder for me to illustrate my point about accessing Salesforce Objects via AngularForce. But that is another conversation.

//TODO

Like all apps, I have a number of things I’d still like to do on Share Wonder.

* Apex REST could be integrated more clearly into the Smart Objects framework. It should be relatively easy to queue requests and to chew through those requests once connected.

* Images are always online right now, which means that image links can break when you go offline. This is not the end of the world in a demo app, but it should be relatively easy to fix.

* I’d like to spend some time creating a background process that downloads posts while you are doing something else in the app. This would make it easier to process Apex REST requests queued up while you were offline.

* I’d like to add pull to refresh to the app.

Impressions

My over all impression of creating a hybrid local app using the SDK 2.0 is that it’s easier than I thought it would be. I have made trade offs in the UI that made it faster for me to develop. I’m not decided yet on whether these speed of development trade-offs impact usability.  Most of the time I spent on the app boiled down to tweaking the UI, and figuring out what the experience should be.  AngularJS has certainly proven itself to be a flexible toolset.

 

 

tagged , , , , Bookmark the permalink. Trackbacks are closed, but you can post a comment.
  • Jorge Mimica

    Hi Reid,

    I loved the app. I was perusing the sf blog looking for a demo app using mobile SDK 2.0 for iOS hybrid-local when I came across this post.

    I’ve just installed the app in my iphone and the app worked flawless. I loved it. It was just what I was looking for. Now I will dig into the code to see how you built it.

    I can’t wait too see the todo features in a v2. I didn’t see any safe harbor statement :)

    Many thanks!