Migrating Existing Projects to Salesforce DX
Identifying conflicts with package folders during a Salesforce DX migration.
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).
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).
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
<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
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)
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.
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” />