Bri Manning

Salesforce Android Documentation

May 31, 2018

I’m currently integrating Salesforce MobilePush for a client. This is a cautionary blog post for someone (including me) who later has to deal with Salesforce integration. After wading through the different documentation for different SDKs without a clear indication when I was in the right place, I finally stumbled on the right one, something oddly named “Journey Builder for Apps Android SDK.”

First, don’t use the Mobile Developer Center, that thing is a nightmare. They somehow want to have you use an npm package to integrate for Android and, for some reason, assume that you don’t already have an app and that you are creating a new one using forcedroid.

That was bad enough, but the highlight was on the page about native Android requirements. It includes this bit: “Minimum API: Android KitKat (API 21).” Now, I fully understand that not everyone is an Android developer, a mobile developer, or even a developer at all so it’s easy to miss, but KitKat is API 19 and Lollipop is API 21. That might seem trifling, but there is a dramatic difference between the two. A significant portion of the OS changed including how permissions work. At this point, most people are targeting Lollipop and above, so it’s not a big deal, but my guess is they don’t support KitKat, but search-and-replaced from API 19 to API 21, but forgot KitKat to Lollipop. At least they replied to my tweet complaining about it.

Back to the documentation I actually wanted to use.

The how-to seemed straightforward enough. It included things that I didn’t need to do since they’re pretty standard (putting an app id in the gradle config and the app class in the manifest, for example). What they did leave out were two required lines that I’m going to include here.

One was pretty simple, when you’re configuring the SDK, you need to specify the notification channel name. It’s not included in the documentation and the initialization won’t happen, but there also won’t be an exception thrown. Given the notification changes that happened with Oreo, this is pretty obvious and it would be nice if the SDK had some smart default instead of failing to initialize if you left this out. It’s just a one line addition in that code block: .setNotificationChannelName("SomeNotificationChannelName"). That’s fine and was pretty easy to find after stepping through the code to see why things weren’t initialized.

After adding that line, you start getting a new error. Yay. It reads:

    java.lang.RuntimeException: An error occurred while executing doInBackground()
        at android.os.AsyncTask$3.done(AsyncTask.java:353)
        at java.util.concurrent.FutureTask.finishCompletion(FutureTask.java:383)
        at java.util.concurrent.FutureTask.setException(FutureTask.java:252)
        at java.util.concurrent.FutureTask.run(FutureTask.java:271)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1162)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:636)
        at java.lang.Thread.run(Thread.java:764)
     Caused by: java.lang.NoClassDefFoundError: Failed resolution of: Lcom/google/android/gms/common/util/zzv;
        at com.google.android.gms.iid.zzo.(Unknown Source:41)
        at com.google.android.gms.iid.zzo.(Unknown Source:2)
        at com.google.android.gms.iid.InstanceID.getInstance(Unknown Source:31)
        at com.google.android.gms.iid.InstanceID.getInstance(Unknown Source:1)
        at com.salesforce.marketingcloud.i.c(Unknown Source:27)
        at com.salesforce.marketingcloud.i.a(Unknown Source:133)
        at com.salesforce.marketingcloud.MCJobService$a.a(Unknown Source:24)
        at com.salesforce.marketingcloud.MCJobService$a.doInBackground(Unknown Source:2)
        at android.os.AsyncTask$2.call(AsyncTask.java:333)
        at java.util.concurrent.FutureTask.run(FutureTask.java:266)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1162) 
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:636) 
        at java.lang.Thread.run(Thread.java:764) 
     Caused by: java.lang.ClassNotFoundException: Didn't find class "com.google.android.gms.common.util.zzv" on path: DexPathList[[zip file "/data/app/com.patientslikeme.android.staging-ItSSAe8hr5sSdmW5PnsfpQ==/base.apk"],nativeLibraryDirectories=[/data/app/com.patientslikeme.android.staging-ItSSAe8hr5sSdmW5PnsfpQ==/lib/x86, /system/lib, /vendor/lib]]
        at dalvik.system.BaseDexClassLoader.findClass(BaseDexClassLoader.java:125)
        at java.lang.ClassLoader.loadClass(ClassLoader.java:379)
        at java.lang.ClassLoader.loadClass(ClassLoader.java:312)
        at com.google.android.gms.iid.zzo.(Unknown Source:41) 
        at com.google.android.gms.iid.zzo.(Unknown Source:2) 
        at com.google.android.gms.iid.InstanceID.getInstance(Unknown Source:31) 
        at com.google.android.gms.iid.InstanceID.getInstance(Unknown Source:1) 
        at com.salesforce.marketingcloud.i.c(Unknown Source:27) 
        at com.salesforce.marketingcloud.i.a(Unknown Source:133) 
        at com.salesforce.marketingcloud.MCJobService$a.a(Unknown Source:24) 
        at com.salesforce.marketingcloud.MCJobService$a.doInBackground(Unknown Source:2) 
        at android.os.AsyncTask$2.call(AsyncTask.java:333) 
        at java.util.concurrent.FutureTask.run(FutureTask.java:266) 
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1162) 
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:636) 
        at java.lang.Thread.run(Thread.java:764) 

Nothing better than getting an error in some obfuscated code.

There wasn’t something obvious in Salesforce pointing toward a solution and I retraced my steps to no avail. However, just taking another look at what the error was, it starts to become more clear. I was missing a Google Android library of some kind, probably a play service. The Salesforce library has an undocumented dependency on the gcm library. I added implementation 'com.google.android.gms:play-services-gcm:15.0.1' to gradle dependencies and I was ready to go. We didn’t already have that included in this app since we’re using Firebase, like many modern apps.

I wanted to record it here for the next person who is off stumbling through terrible Salesforce documentation.