Tips & Tricks for Building Hybrid Remote Mobile Apps using Salesforce Mobile SDK & Cordova

The Salesforce Mobile SDK is an extremely useful boilerplate for creating mobile apps on iOS and Android for Salesforce (and Windows now too); in fact, it is so easy to setup that I’ve used it to prototype out some quick mobile apps for Salesforce Communities. The SDK supports 3 different flavors of mobile apps: fully native, hybrid local, and hybrid remote. Native and hybrid local require you to write an app outside of Visualforce – whether you choose to use Objective-C / Java for a native app or Javascript for a hybrid local app is up to you.

The third option involves using the SDK to create a mobile wrapper of your existing Salesforce app by pointing to your production or sandbox and providing a start URL to redirect to after login (see here for a quick start guide). Visualforce-based responsive apps are perfect for the hybrid remote option that the SDK offers, which essentially wraps your Visualforce site in a wrapper that lets you distribute an app but make all of the future updates (outside the basic wrapper setup) via Visualforce. All of the Objective-C or Java “native” code has already been written by Salesforce, so unless you want to do something unique (like push notifications) you won’t write a single line of code.

While the basic setup is very simple (there is actually no code changes, just a few modifications to XML or json files for configurations), I did run into quite a few brain teasers that, as a novice to Cordova, took me longer than I wish to admit to figure out. Below are some of the highlights in hopes that it saves someone else some time.

Setting up the Mobile App to point to a Community

This isn’t so much of a holdup, as the documentation does tell you how to modify the URL that you authenticate against (production vs sandbox) for iOS and Android separately. You’ll need to instead point to the custom URL of your community if you want to go that route rather than login.salesforce.com or test.salesforce.com. Note that your internal Salesforce users will not be able to authenticate to the mobile app (nor would they be able to authenticate against the community login page anyway).

Displaying the Status Bar in iOS

Another well documented item, but just a note that you’ll need to add a Cordova plugin to actually show the status bar in iOS (otherwise your users won’t know what time it is). This isn’t necessary for Android.

Debugging

As a bit of a novice with iOS (and being completely green with Android), debugging the emulator (and my actual device) was an interesting exercise. Considering that I was doing essentially all of my development in Visualforce, I needed a debug console you typically find in a browser. Fortunately both iOS and Android provide some solid tools based on their respective browsers. I leveraged this quick, informational guide for my own setup (while this references using actual devices, the setup steps for the browsers are applicable for emulators).

For iOS, assuming you’ve setup an Xcode project you can run the application in the standard iOS simulator or on your device. To debug your applications HTML / Javascript based pages, you can open Safari’s development tools to access the console and then choose the current page.

Safari Debug

If you use the standard Android emulator you can debug your hybrid application using Chrome. I found the Android emulator extremely slow; it may be that there is some sort of fine tuning I can do with the settings, but I thought my settings were pretty generous and I have a pretty modern computer. For most of my actual testing I used Genymotion, though I didn’t get around to figuring out how to debug that emulator (I debugged the joint bugs on the iOS emulator, then verified they worked in Genymotion).

Including cordova.js in Visualforce

As part of your implementation, you will need to include cordova.js in your Visualforce page as one of the includes. This file is the library that allows your Visualforce-based pages to access any native code that the SDK has (such as managing user sessions, authentication, logout, using the camera, etc.) via different Cordova plugins. The SDK automatically generates this file separately for iOS and Android, along with a couple standard plugins. The documentation notes that you can simply reference //localhost/cordova.js in your Visualforce

Unfortunately, while this seemed to work fine on my emulators, once I deployed to an actual device I did not have the same luck (even when I whitelisted * in the config.xml). I was able to debug my application to the point of realizing cordova.js was not loading. While it is a bit kludgy, I ended up taking the contents of the www folder (separately for iOS and Android), zipping them up into a static resource, and serving them up directly from Salesforce.

While there might be a good workaround, I spent too much time trying to debug why there was a discrepancy (note that this was the 2.3 version of the SDK – as of now there is a 3.x version).

Intelligently loading the right cordova.js – at the right time

As mentioned above, there are separate cordova.js files (and folders) for iOS and Android. You’ll want to ensure you load the right one at the right time. You also don’t want to load cordova.js when you aren’t in a mobile app – it isn’t doing anything and is a wasted resource load. In fact, it created some oddities when it was loading for Internet Explorer, as IE on a tablet seemed to think it was an app that should be opened.

In order to know whether you are in a mobile app (rather than desktop or a responsive mobile browser), you can reference the User Agent. The easiest way to handle this is using Apex and check the headers of the current page’s PageReference for SalesforceMobileSDK.

public Boolean getIsMobileApp() {
  String userAgent = ApexPages.currentPage().getHeaders().get('User-Agent');
  return String.isNotBlank(userAgent) && userAgent.containsIgnoreCase('SalesforceMobileSDK');
}

Once you have this setup, you can wrap your script include in an (ensure the layout is set to **none**) and set the rendered attribute to the result of that method.

<apex:outputPanel layout="none" rendered="{!isMobileApp}">
  <apex:includeScript value="{!URLFOR($Resource.cordova, 'cordova.js')}"></apex:includeScript>
</apex:outputPanel>

In order to determine whether you are on Android or iOS, you’ll use a very similar method that also checks the User Agent for iPhone / iPad and uses a pair of tags to differentiate what file to load. You can simply program one method to check for either iOS or Android (I found checking for iPhone / iPad easier) and then assume if you are in the mobile app but it is not iOS, it must be Android.

External URLs

You’ll quickly find that if you have a URL to an external site, even if you set up your links to open in a new tab on your site, the mobile app will simply open them in the same web view. Since your mobile app has no back button, you’ll be stuck on that external link and can no longer access your mobile site (and will need to force the app to close to reset it).

The easiest way to rectify that is the InAppBrowser plugin. By using the window.open() javascript method, the InAppBrowser plugin will intercept your links and act upon them accordingly (note that it will not automatically evaluate any  tags with URL href attributes).

For my purposes, I wanted to future proof the implementation a bit so I set up a Javascript click handler on all tags rather than assuming future developers would always use window.open() instead. This handler had a selector that checked for any URLs that had http/https prefixes (my internal pages are always relative paths) or _target=”_blank” _to denote external URLs, though the use cases may vary amongst projects. In addition, I ensured that I only made modifications if the User Agent notified me that I was in a mobile app (similar to the section about loading cordova.js above)

External URLs…and Android

While the InAppBrowser worked really well in iOS right from the start, I had a problem getting it to work on Android. It turns out, Android _requires _the location attribute on window.open() to be set to true. I had been setting it to false on iOS as it seemed unnecessary to give users the ability to type in a URL in a popup browser meant to show a single page. However, the X to close the popup browser on Android apparently is on the same toolbar as the URL, whereas in iOS they are in different sections. This one took me far too long to realize, as the documentation doesn’t seem to explicitly state it is required (even though all the examples use it). I ended up happening upon a random message board post about it after a lengthy search, which I can’t even locate again to post a link to.

Uploading your APK for Google Play

As of now there is a known issue with Google around the AndroidManifest.xml file that requires you to remove some tokens in your XML file and replace them with hardcoded values to get the upload to the Google Play developer site to work. See here for the GitHub ticket, and some comments around the best way to workaround for the time being (hopefully this gets fixed, so don’t forget to switch it back in the future).

To summarize, you’ll need to replace references to <category android:name=”@string/app_package” /> with <category android:name=”com.salesforce.androidsdk” />