6

I've been trying for days now to get a working Remote Hybrid Android app to build for Android using the current (4.1) build of the Salesforce Mobile SDK. I've made some progress (I think), but remain stuck. This is a log of my progress so far.

Using forcedroid to create an app skeleton currently results in errors, as has been documented in the Salesforce Mobile SDK Google Group.

nvm use v0.12.2
forcedroid create --apptype=hybrid_remote --appname=sdktest --targetdir=. \
--packagename=org.jclark.sdktest --startpage=/apex/BasicVFPage

eventually leads to:

Fetching plugin "phonegap-plugin-push" via npm
Installing "phonegap-plugin-push" for android
Failed to install 'phonegap-plugin-push':Error: Variable(s) missing: SENDER_ID
    at /Users/jclark/.nvm/versions/node/v0.12.2/lib/node_modules/cordova/node_modules/cordova-lib/src/plugman/install.js:346:23
    at _fulfilled (/Users/jclark/.nvm/versions/node/v0.12.2/lib/node_modules/cordova/node_modules/q/q.js:787:54)
    at self.promiseDispatch.done (/Users/jclark/.nvm/versions/node/v0.12.2/lib/node_modules/cordova/node_modules/q/q.js:816:30)
    at Promise.promise.promiseDispatch (/Users/jclark/.nvm/versions/node/v0.12.2/lib/node_modules/cordova/node_modules/q/q.js:749:13)
    at /Users/jclark/.nvm/versions/node/v0.12.2/lib/node_modules/cordova/node_modules/q/q.js:557:44
    at flush (/Users/jclark/.nvm/versions/node/v0.12.2/lib/node_modules/cordova/node_modules/q/q.js:108:17)
    at process._tickCallback (node.js:355:11)
Failed to install 'com.salesforce':Error: Variable(s) missing: SENDER_ID
    at /Users/jclark/.nvm/versions/node/v0.12.2/lib/node_modules/cordova/node_modules/cordova-lib/src/plugman/install.js:346:23
    at _fulfilled (/Users/jclark/.nvm/versions/node/v0.12.2/lib/node_modules/cordova/node_modules/q/q.js:787:54)
    at self.promiseDispatch.done (/Users/jclark/.nvm/versions/node/v0.12.2/lib/node_modules/cordova/node_modules/q/q.js:816:30)
    at Promise.promise.promiseDispatch (/Users/jclark/.nvm/versions/node/v0.12.2/lib/node_modules/cordova/node_modules/q/q.js:749:13)
    at /Users/jclark/.nvm/versions/node/v0.12.2/lib/node_modules/cordova/node_modules/q/q.js:557:44
    at flush (/Users/jclark/.nvm/versions/node/v0.12.2/lib/node_modules/cordova/node_modules/q/q.js:108:17)
    at process._tickCallback (node.js:355:11)
Error: Variable(s) missing: SENDER_ID

If you attempt to run the app now (cordova run android), the app will build and run, however, you'll get a ERR_FILE_NOT_FOUND (file:///android_asset/www/index.htmlpopup, which I would not expect from a Remote hybrid app.

However, the error text indicates that the push plugin was not installed correctly, and looking at the command output and the source of forcedroid convinced me that some of the installation steps are being skipped when the plugin install fails. Some folks in the MobileSDK Google Group reported removing the new phonegap-plugin-push and adding the older salesforce push plugin, but I could not get a working build this way, so I set out to fix the underlying forcedroid error.

The root issue is that the new push plugin, phonegap-plugin-push, expects a variable SENDER_ID to be defined, containing the Android Push Notification Client Id; the plugin installation docs show the variable being passed as part of the cordova plugin add commandline. Unfortunately, the push plugin isn't added with that command. Instead, forcedroid adds the plugin SalesforceMobileSDK-CordovaPlugin, which lists the push plugin as a dependency in its plugin.xml. A little experimentation showed that I could add the variable to the dependency declaration in such a way that it would use an ENV variable of the same name. I forked the plugin and made that change, then edited my local forcedroid (in my case, /Users/jclark/.nvm/versions/node/v0.12.2/forcedroid to change the plugin add line:

//shelljs.exec('cordova plugin add https://github.com/forcedotcom/SalesforceMobileSDK-CordovaPlugin#v' + version);
shelljs.exec('cordova plugin add https://github.com/jclark-dot-org/SalesforceMobileSDK-CordovaPlugin');

With this change in place, running forcedroid with a SENDER_ID ENV variable declared:

SENDER_ID=0000000000 forcedroid create --apptype=hybrid_remote --appname=sdktest --targetdir=. --packagename=org.jclark.sdktest --startpage=/apex/BasicVFPage 

Runs without error. Running the app (cordova run android) will succeed like the first test (app runs, cannot find index.html), but further experimentation has shown that there's still an issue with the basic app framework... the after-platform-add-android hook of the Salesforce plugin (plugins/com.salesforce) never runs. This script makes some changes to the AndroidManifest.xml, such as commenting out the first <application> tag found - presumably so that it can be replaced by the version in the Salesforce plugin's plugin.xml file. Adding any plugin will fire this hook, and then running cordova prepare android will cause AndroidManifest.xml to be updated. For example:

cordova plugin add cordova-plugin-badge
cordova prepare android

Now an attempt to build the app will fail:

:processDebugManifest
/Users/jclark/sdktest/platforms/android/AndroidManifest.xml:30:9 Error:
    Element activity#com.adobe.phonegap.push.PushHandlerActivity at AndroidManifest.xml:30:9 duplicated with element declared at AndroidManifest.xml:6:9
/Users/jclark/sdktest/platforms/android/AndroidManifest.xml:0:0 Error:
    Validation failed, exiting

The reason for this is somewhat complicated, but this is what I've determined: when the after-platform-add-android hook runs, it removes the first <activity> tag from AndroidManifest.xml, which the salesforce plugin replaces (see above), however, this changes the order of the tags within the <application> tag. The activity which is removed and replaced is the "primary" activity (containing android:name="com.salesforce.androidsdk.phonegap.ui.SalesforceDroidGapActivity"); It is originally the first child of application, but after the manifest is updated it becomes the last child. This makes <activity android:exported="true" android:name="com.adobe.phonegap.push.PushHandlerActivity" /> become the first child. Then the cordova prepare step updates the first activity element found to set the android:launchMode explicitly, so the activity becomes <activity android:exported="true" android:launchMode="singleTop" android:name="com.adobe.phonegap.push.PushHandlerActivity" />. However (and this is where I'm theorizing based on empirical evidence), it looks like the build process is re-writing the manifest by adding missing items; since it expects the version without android:launchMode="singleTop", it ends up adding it again. I tried to work around this by adding android:launchMode="singleTop" to the main activity in the plugins.xml file (a change which remains in my fork, since it was in the original activity), but that doesn't stop this from happening. Since I didn't want to fork the push plugin to change the plugin.xml to add the launchMode attrib, the workaround I've found is to manually edit the android.json file to add it there. Then I remove all of the of the contents of <application> from the manifest, and run cordova prepare android to rebuild the manifest.

This moves past the prior error, but fails with:

/Users/jclark/sdktest/platforms/android/build/intermediates/manifests/full/debug/AndroidManifest.xml:45: error: Error: No resource found that matches the given name (at 'icon' with value '@drawable/sf__icon').

I addressed this with one more change to my fork of the SalesforceMobileSDK-CordovaPlugin, by updating the after-platform-add-android hook to change sf__icon to icon.

So at this point, I can set the ENV var SETTING_ID, run my custom forcedroid which uses my fork of the SalesforceMobileSDK-CordovaPlugin, add a plugin, run prepare, modify platforms/android/android.json to add android:launchMode=\"singleTop\" to the com.adobe.phonegap.push.PushHandlerActivity line, and run prepare once more to get a project that will build. However, when I run it on device, I immediately get "Unfortunately the application has stopped". logcat shows the following:

E/AndroidRuntime( 5009): FATAL EXCEPTION: main
E/AndroidRuntime( 5009): Process: org.jclark.sdktest, PID: 5009
E/AndroidRuntime( 5009): java.lang.RuntimeException: Unable to instantiate application com.salesforce.androidsdk.phonegap.app.HybridApp: java.lang.ClassNotFoundException: Didn't find class "com.salesforce.androidsdk.phonegap.app.HybridApp" on path: DexPathList[[zip file "/data/app/org.jclark.sdktest-1.apk"],nativeLibraryDirectories=[/data/app-lib/org.jclark.sdktest-1, /vendor/lib, /system/lib]]
E/AndroidRuntime( 5009):        at android.app.LoadedApk.makeApplication(LoadedApk.java:516)
E/AndroidRuntime( 5009):        at android.app.ActivityThread.handleBindApplication(ActivityThread.java:4327)
E/AndroidRuntime( 5009):        at android.app.ActivityThread.access$1500(ActivityThread.java:135)
E/AndroidRuntime( 5009):        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1256)
E/AndroidRuntime( 5009):        at android.os.Handler.dispatchMessage(Handler.java:102)
E/AndroidRuntime( 5009):        at android.os.Looper.loop(Looper.java:136)
E/AndroidRuntime( 5009):        at android.app.ActivityThread.main(ActivityThread.java:5027)
E/AndroidRuntime( 5009):        at java.lang.reflect.Method.invokeNative(Native Method)
E/AndroidRuntime( 5009):        at java.lang.reflect.Method.invoke(Method.java:515)
E/AndroidRuntime( 5009):        at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:779)
E/AndroidRuntime( 5009):        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:595)
E/AndroidRuntime( 5009):        at dalvik.system.NativeStart.main(Native Method)
E/AndroidRuntime( 5009): Caused by: java.lang.ClassNotFoundException: Didn't find class "com.salesforce.androidsdk.phonegap.app.HybridApp" on path: DexPathList[[zip file "/data/app/org.jclark.sdktest-1.apk"],nativeLibraryDirectories=[/data/app-lib/org.jclark.sdktest-1, /vendor/lib, /system/lib]]
E/AndroidRuntime( 5009):        at dalvik.system.BaseDexClassLoader.findClass(BaseDexClassLoader.java:56)
E/AndroidRuntime( 5009):        at java.lang.ClassLoader.loadClass(ClassLoader.java:497)
E/AndroidRuntime( 5009):        at java.lang.ClassLoader.loadClass(ClassLoader.java:457)
E/AndroidRuntime( 5009):        at android.app.Instrumentation.newApplication(Instrumentation.java:975)
E/AndroidRuntime( 5009):        at android.app.LoadedApk.makeApplication(LoadedApk.java:511)
E/AndroidRuntime( 5009):        ... 11 more

The key bit of course is Didn't find class "com.salesforce.androidsdk.phonegap.app.HybridApp". It appears that the app is being built without all of the SDK components included. At this point I've exhausted my knowledge of the Cordova/Android build process. I tried adding the SDK libs directly to platforms/android/project.properties, e.g.:

android.library.reference.2=SalesforceSDK
android.library.reference.3=SalesforceHybrid
android.library.reference.4=SmartStore
android.library.reference.5=SmartSync

But that just led to more build errors; I'm thinking that I'm going in the wrong direction there... the plugin is part of the project, so I shouldn't need to add explicit references.

At this point, I'm not sure what to try next. My ideal solution would be a working forcedroid that can build a working Hybrid Remote android app from scratch, supports push, and continues to work when other plugins are added. Once I have a set of steps to do this, I can work on my original problem of porting an existing SDK 3.x project to SDK 4.1. I will be cross-posting this to the SDK Google Group as well.

Update 3/22: On the Salesforce MobileSDK Google Group thread for this post, SDK Dev Bharath Hariharan pointed me to a recent commit in the unstable branch of the Cordova Plugin that addresses the first issue (push plugin errors) by pinning the versions of all dependencies. I updated my local forcedroid to use the latest unstable commit:

//shelljs.exec('cordova plugin add https://github.com/forcedotcom/SalesforceMobileSDK-CordovaPlugin#v' + version);
//shelljs.exec('cordova plugin add https://github.com/jclark-dot-org/SalesforceMobileSDK-CordovaPlugin');
shelljs.exec('cordova plugin add https://github.com/forcedotcom/SalesforceMobileSDK-CordovaPlugin#a45425d8e831c9567a0d0bf8b05bc3e1b19b4f23');

This fixes the first problem (push plugin errors). While testing this, I found that for some reason the android post install script wasn't finding shelljs, so I added it locally as suggested in the script error message (npm install shelljs@0.5.3); this allowed all post-run scripts to run successfully during the forcedroid run, and eliminates the need to add another plugin to get a fully updated AndroidManifest.xml. So at this point, I can run the updated forcedroid using the latest unstable SDK Cordova plugin, run cordova prepare android to cause the duplicate activity issue, and manually add the launchmode to the push activity in android.json and remove the resulting duplicate activity in AndroidManifest to get a build that will install on the device. It still crashes on launch with the Didn't find class "com.salesforce.androidsdk.phonegap.app.HybridApp" exception.

2

I have found that if my project includes plugins other than the SF one then I need to make sure that I add the SF one last. I have mentioned this in one of the git issues. At the time of writing I still have to follow the process of removing the plugin and platform and then re-adding both, only this way do I not end up with a duplicate issue in the manifest.xml.

As an aside yes the push plugin update to v1.6 was a right pain ;-)

  • We have to do this same process, readding the SF plugin last – Phil B Jul 12 '17 at 17:23

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.