This is the fourth and final installment in a series of blog posts describing how we built Cloud Hunter - the scavenger hunt mobile app for Dreamforce 2012. The first post covered the basic application use case and architecture. In the second post, I described how we converted a Visualforce-based web version of Cloud Hunter into hybrid Android and iOS applications using the Salesforce Mobile SDK. The third post covered how the hybrid Cloud Hunter app implemented picture and signature capture. Let’s wrap-up by reviewing how Cloud Hunter implemented check-ins (i.e. geolocation) and QR/Bar code scanning.

 

Check-ins and Geolocation

Check-in missions require players to use their mobile phones to check-in at or near a given landmark (e.g. Moscone West). To implement this mission type, Cloud Hunter used a combination of the PhoneGap Geolocation API on the client-side and the new Force.com Geolocation field type on the server-end. Lets start on the client side with a JavaScript snippet from the Mission page.

        function checkInMissionAction(){
            $j.mobile.showPageLoadingMsg("a", "Working....", false);
            navigator.geolocation.getCurrentPosition(onCheckInSuccess,
                                                     onCheckInFailure,
                                                     { enableHighAccuracy: true });
        }

        function onCheckInSuccess(position) {
            var missionId =  '{!Mission__c.id}';
        	//Invoke the JS Remoting function to complete the check-in.
            Visualforce.remoting.Manager.invokeAction(
                     '{!$RemoteAction.MissionControllerExtension.completeCheckInMissionType}',
                     position.coords.latitude,
                     position.coords.longitude,
                     missionId,
                     '{!gameId}',
                     function(res,event) {
                        $j.mobile.hidePageLoadingMsg();
                        ....
                     });
        }

Line 3 shows how we use of the getCurrentPosition PhoneGap API to determine the current latitude/longitude co-ordinates of the mobile device. Under the covers, PhoneGap uses the best available device feature (GPS/signal location etc.) to return the current location of the device. Line 11 shows how we then use JS Remoting to pass along the current lat/long co-ordinates to the completeCheckInMissionType method in the Apex extension class. Let’s look at that method next.

public with sharing class MissionControllerExtension {
    ....
    @RemoteAction
    public static Decimal completeCheckInMissionType(Decimal lat, Decimal lon,
                                                     String missionId, String game) {
        Mission__c mission = [select place__c, place__r.Distance_Tolerance__c,
                              place__r.Location__latitude__s, place__r.Location__longitude__s,
                              place__r.name from Mission__c where id = :missionId limit 1];

        if (mission.place__r != null && mission.place__r.Location__latitude__s != null){
            String soql = 'select id from Place__c '+
                          'where DISTANCE(Location__c, GEOLOCATION('+lat+','+lon+'), \'km\') <'
                           + mission.place__r.Distance_Tolerance__c/1000 +
                          ' and id=\''+mission.place__c+'\'';
            List<Place__c> nearbyPlaces = Database.query(soql);
            if (nearbyPlaces.size() == 1){
                Achievement__c completedMission = completeMission(game, missionId);
                ...
                return completedMission.Points_Awarded__c;
            }
            else{
                return -1;
            }
        }
        return -1;
    }
    ....
}

The snippet above shows how Cloud Hunter uses the new Geolocation field type to determine if the lat/long co-ordinates passed in from the mobile app are close enough to the designated check-in location. The lat/long co-ordinates for the designated check-in location are stored in the Location field of the Place custom object (Check-in Missions are required to have an associated Place record). As shown in the screenshot on the right, the Location field is of type ‘Geolocation’ which means that we can store lat/long co-ordinates in either degrees or decimal format.

That’s not what makes the new Geolocation field interesting though. After all, you can simply store lat/long co-ordinates in Force.com in two separate custom fields. What’s cool about using a Geolocation field is what you can do with it in Apex and SOQL. Line 7 shows the special Custom_Field__Latitude/Longitude__s SOQL syntax needed to query Geolocation fields like Location (check out Josh’s blog post for more details). That query return the lat/long co-ordinates of the designated check-in point for the Mission. Next, on Line 11 we construct a dynamic SOQL query that uses the new GEOLOCATION and DISTANCE functions to run a query that determines if the lat/long co-ordinates passed in from the mobile app are within a certain radius (specified in the Distance_Tolerance__c field on the Place record) of the designated check-in point. Previously, you would have had to write some complex Apex logic (and needed a PhD in Math to boot!) in order to perform this kind of ‘find nearby’ logic. Now, you can simply define a Geolocation field and use the new SOQL geolocation functions to perform such queries.

If our dynamic SOQL query determines that the designated Place is close enough to the check-in co-ordinates, the method returns the total number of points awarded. Otherwise it returns -1 to indicate a failed check-in.

 

QR/Bar code scanning

The fourth and final Mission type supported by Cloud Hunter is Scanner. These missions require the player to scan a specific QR or Bar code. Scanning a barcode (which would require access to the device camera) is unfortunately not one of the core APIs supported by PhoneGap. The good news however is that PhoneGap/Cordova has a flexible plugin architecture whereby developers can add their own custom plugins to supports functions like barcode scanning that are not part of the core library. All of these plugins are open-source and so supporting this mission type was just a matter of integrating the Barcode PhoneGap plugin into the hybrid version of Cloud Hunter. Here’s how we did it.

  1. Download the GitHub repo for the PhoneGap plugins to your local machine (using git clone or clicking the ‘ZIP’ icon). Depending on which mobile platform you’re developing for, follow the instructions in the readme file to import the barcode scanner plugin in your Android or iOS project. Note that the Android scanner plugin has a different codebase for Cordova 1.8.1 based apps vs Cordova 2.0 apps. The Salesforce Mobile SDK 1.3 (which is the version that Cloud Hunter was built with) bundles Cordova 1.8.1 and so make sure you use that branch of the plugin code.
  2. Import the barcodescanner.js file that is included in the PhoneGap plugin repo into your Visualforce page. Here is how Cloud Hunter does it in the Mission Visualforce page.
    <apex:dynamicComponent componentValue="{!BarCodeScannerJS}"/>

    The second blog in this series covers why we need a Dynamic Visualforce Component to import the PhoneGap JavaScript library.

  3. The following JavaScript snippet from the Mission page shows how we can now invoke the PhoneGap plugin to scan a QR or bar code.
            function scannerMissionAction() {
                window.plugins.barcodeScanner.scan( onBarcodeScanSuccess,
                                                    onBarcodeScanFailure);
            }
    
            function onBarcodeScanSuccess(result) {
                    var missionId =  '{!Mission__c.id}';
                    $j.mobile.showPageLoadingMsg("a", "Working....", true);
                    Visualforce.remoting.Manager.invokeAction(
                          '{!$RemoteAction.MissionControllerExtension.completeScanMissionType}',
                          result.text,
                          missionId,
                          '{!gameId}',
                          function(res,event){
                                $j.mobile.hidePageLoadingMsg();
                                ....
                          });
            }

    Line 2 invokes the custom PhoneGap plugin with a single line of JS code. If the QR/bar code scan is successful, the success callback function get invoked with the scanned text (‘result.text’ above). We then use a JavaScript Remoting call to pass the scanned text to the completeScanMissionType extension method where it is matched against the expected value.

 

Hopefully these series of blog posts have helped you dive under the covers of Cloud Hunter and understand the various features of the Salesforce Touch Platform and the Hybrid Mobile SDK in particular. As always, comments, questions and suggestions on how we can improve the application are welcome.

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

    Greate pieces. Keep writing such kind of info on your site.
    Im really impressed by your site. Hey there, You have performed an incredible job.

    Mobile Apps Development

  • http://profile.yahoo.com/WRLE5MDGVQ2XMCVOKIPC73FDP4 Kevin M

    Is there a way you can customize the login screen for hybrid apps?

  • resourcesuites

    is there a way to implement barcode/qrcode/logo scanning at time of photo capture and uploading?

    • arronlee

      Can it be possible using some manual barcode scanning SDK which can be customized by users according to their own favors? I am testing with the related tools these days.

  • cindy

    any suggestion for qr code reader app for iphnoe ?