Things that you need to know if you want to develop an SAP Kapsel hybrid app for modern smartphone
You will never stop learning new things. This should be something engraved and highlighted everywhere.
We’re developing a hybrid SAPUI5 application with Kapsel support for one of our client. To be honest this project is a little bit old and is not leveraging the newest feature of SAP WebIDE that allows you to create the hybrid build on the cloud. Out team stepped into the development of the project only in the final phase where the structure and the build environment was already decided months ago (and this is the main problem).
Anyway, this project is made of a SAPUI5 application subdivided in 4 different applications (4 tiles). As I said it has started months ago so they didn’t use WebIDE mobile services. Instead, they used MDK (Mobile Development Kit) 3.0.
The problem was in the offline synchronization phase. When you start the app you see a login page where the app auth with the IdP connected to our client’s on-premise landscape though SAP Mobile Services via the Cloud Connector.
When the user “Sign in” the app auths you and the synchronization with the XSA OData service start. This part is all automatically handled by the Kapsel’s OData plugin that downloads all the data to be used offline.
On our test device, a Samsung Galaxy S4 mini, everything works like a charm. The problem happened when I switched to test the app on my Samsung S8. After the login phase, the app creates a BusyDialog while it syncs with the XSOData service. On my S8 the app loops on that BusyDialog forever.
Debug Debug Debug
E/SMP_LOGON: Certificate info of certificate retrieved from Logon Manager is null E/SMP_AUTH_PROXY: Getting certificate from Logon Manager failed due to InvocationTargetException: null I/com.sap.smp.authflows: [group: com.sap.smp.sdk.android] [artifact: HttpConvAuthFlows] [version: 3.15.3] [buildTime: 2017:07:19:12:13] [gitCommit: 48bd516ede62773b0ecb863646c41130c7767c29] [gitBranch: n/a] D/shim: loading shared libraries W/System.err: java.lang.UnsatisfiedLinkError: dalvik.system.PathClassLoader[DexPathList[[zip file "/data/app/com.techedge.mfspp-H-1jvpRUjO-CftPguTjTvA==/base.apk", zip file "/data/app/com.techedge.mfspp-H-1jvpRUjO-CftPguTjTvA==/split_lib_dependencies_apk.apk", zip file "/data/app/com.techedge.mfspp-H-1jvpRUjO-CftPguTjTvA==/split_lib_resources_apk.apk", zip file "/data/app/com.techedge.mfspp-H-1jvpRUjO-CftPguTjTvA==/split_lib_slice_0_apk.apk", zip file "/data/app/com.techedge.mfspp-H-1jvpRUjO-CftPguTjTvA==/split_lib_slice_1_apk.apk", zip file "/data/app/com.techedge.mfspp-H-1jvpRUjO-CftPguTjTvA==/split_lib_slice_2_apk.apk", zip file "/data/app/com.techedge.mfspp-H-1jvpRUjO-CftPguTjTvA==/split_lib_slice_3_apk.apk", zip file "/data/app/com.techedge.mfspp-H-1jvpRUjO-CftPguTjTvA==/split_lib_slice_4_apk.apk", zip file "/data/app/com.techedge.mfspp-H-1jvpRUjO-CftPguTjTvA==/split_lib_slice_5_apk.apk", zip file "/data/app/com.techedge.mfspp-H-1jvpRUjO-CftPguTjTvA==/split_lib_slice_6_apk.apk", zip file "/data/app/com.techedge.mfspp-H-1jvpRUjO-CftPguTjTvA==/split_lib_slice_7_apk.apk", zip file "/data/app/com.techedge.mfspp-H-1jvpRUjO-CftPguTjTvA==/split_lib_slice_8_apk.apk", zip file "/data/app/com.techedge.mfspp-H-1jvpRUjO-CftPguTjTvA==/split_lib_slice_9_apk.apk"],nativeLibraryDirectories=[/data/app/com.techedge.mfspp-H-1jvpRUjO-CftPguTjTvA==/lib/arm64, /data/app/com.techedge.mfspp-H-1jvpRUjO-CftPguTjTvA==/base.apk!/lib/arm64-v8a, /data/app/com.techedge.mfspp-H-1jvpRUjO-CftPguTjTvA==/split_lib_dependencies_apk.apk!/lib/arm64-v8a, /data/app/com.techedge.mfspp-H-1jvpRUjO-CftPguTjTvA==/split_lib_resources_apk.apk!/lib/arm64-v8a, /data/app/com.techedge.mfspp-H-1jvpRUjO-CftPguTjTvA==/split_lib_slice_0_apk.apk!/lib/arm64-v8a, /data/app/com.techedge.mfspp-H-1jvpRUjO-CftPguTjTvA==/split_lib_slice_1_apk.apk!/lib/arm64-v8a, /data/app/com.techedge.mfspp-H-1jvpRUjO-CftPguTjTvA==/split_lib_slice_2_apk.apk!/lib/arm64-v8a, /data/app/com.techedge.mfspp-H-1jvpRUjO-CftPguTjTvA==/split_lib_slice_3_apk.apk!/lib/arm64-v8a, /data/app/com.techedge.mfspp-H-1jvpRUjO-CftPguTjTvA==/split_lib_slice_4_apk.apk!/lib/arm64-v8a, /data/app/com.techedge.mfspp-H-1jvpRUjO-CftPguTjTvA==/split_lib_slice_5_apk.apk!/lib/arm64-v8a, /data/app/com.techedge.mfspp-H-1jvpRUjO-CftPguTjTvA==/split_lib_slice_6_apk.apk!/lib/arm64-v8a, /data/app/com.techedge.mfspp-H-1jvpRUjO-CftPguTjTvA==/split_lib_slice_7_apk.apk!/lib/arm64-v8a, /data/app/com.techedge.mfspp-H-1jvpRUjO-CftPguTjTvA==/split_lib_slice_8_apk.apk!/lib/arm64-v8a, /data/app/com.techedge.mfspp-H-1jvpRUjO-CftPguTjTvA==/split_lib_slice_9_apk.apk!/lib/arm64-v8a, /system/lib64, /system/vendor/lib64]]] couldn't find "libodataofflinejni.so" W/System.err: at java.lang.Runtime.loadLibrary0(Runtime.java:1011) at java.lang.System.loadLibrary(System.java:1657) W/System.err: at com.sap.smp.client.odata.offline.common.OfflineStore.load(OfflineStore.java:78) W/System.err: at com.sap.smp.client.odata.offline.common.OfflineStore.<clinit>(OfflineStore.java:67) W/System.err: at com.sap.mp.cordova.plugins.odata.OData.openOfflineStore(OData.java:264) W/System.err: at com.sap.mp.cordova.plugins.odata.OData.execute(OData.java:130) W/System.err: at org.apache.cordova.CordovaPlugin.execute(CordovaPlugin.java:98) W/System.err: at org.apache.cordova.PluginManager.exec(PluginManager.java:132) W/System.err: at org.apache.cordova.CordovaBridge.jsExec(CordovaBridge.java:57) W/System.err: at org.apache.cordova.engine.SystemExposedJsApi.exec(SystemExposedJsApi.java:41) W/System.err: at android.os.MessageQueue.nativePollOnce(Native Method) at android.os.MessageQueue.next(MessageQueue.java:325) W/System.err: at android.os.Looper.loop(Looper.java:142) at android.os.HandlerThread.run(HandlerThread.java:65)
Ok. Now we know it the problem is on the native side. But we also have another two hints if we look at the error carefully:
- couldn’t find “libodataofflinejni.so”: it means that Android didn’t find a native library (c++ libs)
- lib/arm64-v8a: it means that Android is looking for that library in the arm64-v8a folder
Perfect, we can now follow a path! I’ve expanded all the source code folders and looked inside the JNI subfolders and I found the problem.
Kapsel has not installed the .so libraries needed by the arm64-v8a CPU architecture used by my S8 and by many other new phones.
At this point I had two possible solutions:
- Try to update the Kapsel libraries and check out if they added support to 64bit CPU architectures
- Build our Android application forcing the 32bit version of the app
The first solution was the perfect one because if so we could have a different build for each different architecture and this means an optimized code for each of them and less MB in the final product. The solution has also some downside, like upgrading on the fly the Kapsel plugins and re-testing everything but the tradeoff was still ok.
We searched on SAP documentation for the updated version of Kapsel but we found an horrible news: Support note 2464028 — Android Offline Odata 64 bits limitations
On 64-bit Android devices (such as Samsung Galaxy S7), OData Offline Store based application crashes.
Reason and Prerequisites
SAP Mobile Platform SDK 3.0 SP14 PL08 OData Offline library for Android and its dependencies only support 32 bit applications. Android Studio automatically builds for the target architecture of the connected device. On 64-bit devices it is required to use 64-bit native libraries.
Application developer has to force the 32-bit build. Applications built for 32-bit target will be able to run on 64-bit devices without any known incident.
In order to force 32-bit build, edit the build gradle file of the application module.
Please find the documentation about how to configure APK for different ABIs(Application Binary Interfaces).
Well, I was devastated. I lost days debugging this problem and the perfect solution to support the 64bit architecture has vanished in seconds. SAP should have advertised this problem in a huge way in each Kapsel post. But I had no time for complaints and the project’s deadline was too near so I started researching how to solve this problem updating the build.gradle build file on my Android project.
I just followed the two steps:
- Add into the defaultConfig the default configuration for the NDK build supporting both “x86” and “armeabi-v7a”
- Comment out the previous buildFlavors configuration that was splitting the build based on the flavor name
With this configuration, that I still need to optimize (sorry but mastering Gradle takes a lot of time, a resource I’m laking a lot lately), our application is now working the client without a problem and our client is happy again 😉