Gradle Integration
When you sign for a license of the Incode SDKs, you will receive access to our SDKs through a username-password pair to be able to integrate the Incode SDKs into your project.
Quick Start​
Depending on where you have configured the repositories for your application, you need to add a new maven
entry. Read the Gradle documentation about declaring dependencies for more information.
repositories {
maven {
url "https://repo.incode.com/artifactory/libs-incode-welcome"
credentials {
username = "organizationUsername"
password = "xxxxxxxxxxxxxxxxxxxx"
}
}
The current version of our SDK also requires jitpack
to be included as part of the available repositories due to internal dependencies:
repositories {
maven { url "https://jitpack.io" }
}
If you encounter issues while building, check out the troubleshooting section.
In your module-level app/build.gradle
, we require at least Java 8 for source
and targetCompatibility
. Add the following code inside your android{}
block to enforce this:
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
Finally, in your module-level app/build.gradle
, add Incode library dependencies as described below.
Library Dependencies​
The Incode Welcome SDK contain several dependencies. The ones to add in your module-level app/build.gradle
will depend on your use cases. These dependencies break down as follows:
dependencies {
...
// Incode Welcome SDK
implementation 'com.incode.sdk:welcome:5.27.0' // Incode Welcome SDK
implementation 'com.incode.sdk:core-light:2.6.3' // Required core dependency
// The following dependencies are optional and needed only in very specific use cases.
// Make sure you are using the features they provide before adding the dependencies below.
implementation 'com.incode.sdk:extensions:1.1.0' // Optional dependency
implementation 'com.incode.sdk:kiosk-login:1.3.4' // Optional dependency
implementation 'com.incode.sdk:nfc:1.3.4' // Optional dependency
implementation 'com.incode.sdk:qr-face-login:1.2.2' // Optional dependency
implementation 'com.incode.sdk:video-streaming:1.5.5' // Optional dependency
implementation 'com.incode.sdk:model-face-recognition:3.0.0' // Optional dependency
implementation 'com.incode.sdk:model-id-face-detection:2.1.0' // Optional dependency
implementation 'com.incode.sdk:model-liveness-detection:3.0.0' // Optional dependency
}
Required
core-light
dependency contains the image processing libraries that are used in the Welcome SDK.Optional
extensions
dependency is only necessary to use Dynamic Localization or a custom Theme Configuration.Optional
kiosk-login
dependency is only necessary if you are usingKiosk Login
feature of the SDK. It also requires additional configuration as described here.Optional
nfc
dependency is only necessary if you are usingNFC Scanning
feature of the SDK. It also requires additional configuration as described here.Optional
qr-face-login
dependency is only necessary if you are usingQR Face Login
feature of the SDK. It also requires additional repositories configuration as described here.Optional
video-streaming
dependency is only necessary if you are usingConference
module or thestreamFrames
feature inIdScan
andSelfieScan
modules.Optional
model-face-recognition
dependency is only necessary if you are using face recognition feature that runs locally on device. This feature is available only for Face Login.Optional
model-id-face-detection
dependency is only necessary if you are using checks for detecting front/back side of ID.Optional
model-liveness-detection
dependency is only necessary if you are using liveness detection feature that runs locally on device.
Advanced Integration​
If you are concerned about keeping your application download size as small as possible, you can utilize Google Play Feature Delivery to download Incode SDK on demand, at runtime, as a Dynamic Feature Module. This then allows you to uninstall the module when/if it is no longer needed.
Note
You need to be using Android App Bundle format when publishing your app. Dynamic Feature Modules are supported on devices running Android 5.0 (API Level 21) or higher.
Note
It is not possible to isolate the whole Incode SDK to a dynamic module. The main (smaller) part needs to be declared as a direct dependency, and the core part (which contains larger files) can be separated into a dynamic module.
Note for Huawei devices without Google Mobile Services:
Google Play Feature Delivery does not work without Google Mobile Services (GMS). Huawei provides its own library for this purpose - Huawei Dynamic Ability - that works with Huawei Mobile Services (HMS). Integration is very similar to Google's; Classes and methods are named similarly.
Here is how you can build an example app for both GMS and HMS:
Step 1​
In your project-level build.gradle
, add the Incode maven repository with provided credentials:
allprojects {
repositories {
...
maven {
url "https://repo.incode.com/artifactory/libs-incode-welcome"
credentials {
username = "organizationUsername"
password = "xxxxxxxxxxxxxxxxxxxx"
}
}
}
...
}
In your module-level app/build.gradle
, add the following code to android{}
closure:
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
In your module-level app/build.gradle
, add Incode library dependencies:
dependencies {
...
// Google Play Core
implementation 'com.google.android.play:core:1.10.3' // There might be a newer version available
// Incode Welcome SDK
api 'com.incode.sdk:welcome:5.27.0'
}
Step 2​
If you have an Application
class, make it extend from SplitCompatApplication
- Java
- Kotlin
import com.google.android.play.core.splitcompat.SplitCompatApplication;
public class BaseApplication extends SplitCompatApplication {
// ...
}
import com.google.android.play.core.splitcompat.SplitCompatApplication
class BaseApplication : SplitCompatApplication() {
// ...
}
If you do not have an Application
class, add the following attribute to the <application>
element in your AndroidManifest.xml
:
<application
android:name="com.google.android.play.core.splitcompat.SplitCompatApplication"
...
>
Step 3​
Create new Dynamic Module in Android Studio:
File
-->New
-->New Module
- Select
Dynamic Feature Module
- Click
Next
- Enter
Module name
, for example:incode_core
- Click
Next
- Set your desired
Module Title
, for example:Incode Core
- Set
Install-time inclusion
toDo not include module at install-time (on-demand only)
- Enable
Fusing
- Click
Finish
to create the Dynamic Module.
Step 4​
Wait for Android Studio to finish syncing.
After the process is complete, take some time to review the changes that have been made to the project by Android Studio:
- You have a new module named
incode_core
- In your module-level
app/build.gradle
, the following line has been added toandroid{}
closure:
dynamicFeatures = [':incode_core']
- In your
app/res/values/strings.xml
file, a string has been added:
<string name="title_incode_core">Incode Core</string>
- This is the user-friendly module name that could potentially be shown to the user.
Step 5​
Open your app/res/values/strings.xml
file
Add the following string resource:
<string name="module_name_incode_core" translatable="false">incode_core</string>
- You will use this string resource when referencing your Dynamic Module.
- Its value needs to match the
Module name
value from Step 3.
Step 6​
Open incode_core/build.gradle
Add the following code to android{}
closure:
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
Add dependencies as described in the Library Dependencies section.
The incode_core
module does not need to contain any other code.
Step 7​
Add the following ndk abi filters to your module-level app/build.gradle
inside defaultConfig{}
closure:
ndk {
abiFilters "arm64-v8a", "armeabi-v7a", "x86", "x86_64"
}
The Gradle configuration is now complete!
Step 8​
Before using the Incode SDK, we need to make sure that Incode Core
Dynamic Module is installed first.
If not, we can make a request to the Google Play Core library to download and install the Dynamic Module.
This is the example code that you can use somewhere in your app module (in an Activity):
- Java
- Kotlin
// ...
private static final int REQUEST_CODE_CONFIRM_MODULE_DOWNLOAD = 0x8A7; // Use your own value
private SplitInstallManager splitInstallManager;
private SplitInstallStateUpdatedListener splitInstallListener;
// ...
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
// ...
splitInstallManager = SplitInstallManagerFactory.create(this);
checkIfIncodeSdkInstalled();
// ...
}
private void onIncodeSdkInstalled() {
// Everything is ready; You can start the Incode SDK now
}
@Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
super.onActivityResult(requestCode, resultCode, data);
// ...
if (requestCode == REQUEST_CODE_CONFIRM_MODULE_DOWNLOAD) {
if (resultCode == RESULT_OK) {
// User accepted; Dynamic Module will soon start installing
} else {
// User denied the installation prompt; Dynamic Module will not be installed
}
}
// ...
}
private void checkIfIncodeSdkInstalled() {
final String moduleName = getString(R.string.module_name_incode_core);
if (splitInstallManager.getInstalledModules().contains(moduleName)) {
// Dynamic feature module is already installed; Done!
onIncodeSdkInstalled();
} else {
// Starting download of dynamic feature module
SplitInstallRequest request = SplitInstallRequest.newBuilder()
.addModule(moduleName)
.build();
if (splitInstallListener != null) {
splitInstallManager.unregisterListener(splitInstallListener);
}
splitInstallListener = new SplitInstallStateUpdatedListener() {
@Override
public void onStateUpdate(@NonNull SplitInstallSessionState state) {
switch (state.status()) {
case SplitInstallSessionStatus.PENDING:
break;
case SplitInstallSessionStatus.REQUIRES_USER_CONFIRMATION:
/*
This may occur when attempting to download a sufficiently large module.
In order to see this, the application has to be uploaded to the Play Store.
Then features can be requested until the confirmation path is triggered.
*/
try {
splitInstallManager.startConfirmationDialogForResult(state, MyActivity.this, REQUEST_CODE_CONFIRM_MODULE_DOWNLOAD);
} catch (IntentSender.SendIntentException e) {
finish();
}
break;
case SplitInstallSessionStatus.DOWNLOADING:
// You can use this method to update a progress indicator, for example:
//progressBar.setMax((int) state.totalBytesToDownload());
//progressBar.setProgress((int) state.bytesDownloaded());
break;
case SplitInstallSessionStatus.INSTALLING:
// Download complete; Installing...
//progressBar.setProgress((int) state.bytesDownloaded());
break;
case SplitInstallSessionStatus.INSTALLED:
if (splitInstallListener != null) {
splitInstallManager.unregisterListener(splitInstallListener);
}
// Done!
onIncodeSdkInstalled();
break;
case SplitInstallSessionStatus.FAILED:
if (splitInstallListener != null) {
splitInstallManager.unregisterListener(splitInstallListener);
}
// Install failed
break;
default:
Log.w(TAG, "Unhandled state:%s", state.status());
break;
}
}
};
splitInstallManager.registerListener(splitInstallListener);
splitInstallManager.startInstall(request);
}
}
// ...
private lateinit var splitInstallManager: SplitInstallManager
private var splitInstallListener: SplitInstallStateUpdatedListener? = null
// ...
override fun onCreate(savedInstanceState: Bundle?) {
// ...
splitInstallManager = SplitInstallManagerFactory.create(this)
checkIfInstalled()
// ...
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
// ...
when (requestCode) {
REQUEST_CODE_CONFIRM_MODULE_DOWNLOAD -> {
if (resultCode == RESULT_OK) {
// User accepted; Dynamic Module will soon start installing
} else {
// User denied the installation prompt; Dynamic Module will not be installed
}
}
}
// ...
}
private fun checkIfInstalled() {
val moduleName = getString(R.string.app_name)
if (splitInstallManager.installedModules.contains(moduleName)) {
onIncodeSdkInstalled()
} else {
// Starting download of dynamic feature module
val request = SplitInstallRequest.newBuilder()
.addModule(moduleName)
.build()
splitInstallListener?.let {
splitInstallManager.unregisterListener(it)
}
splitInstallListener = SplitInstallStateUpdatedListener { state ->
when (state.status()) {
SplitInstallSessionStatus.REQUIRES_USER_CONFIRMATION -> {
/*
This may occur when attempting to download a sufficiently large module.
In order to see this, the application has to be uploaded to the Play Store.
Then features can be requested until the confirmation path is triggered.
*/
try {
splitInstallManager.startConfirmationDialogForResult(
state,
this,
REQUEST_CODE_CONFIRM_MODULE_DOWNLOAD
)
} catch (_: IntentSender.SendIntentException) {
finish()
}
}
SplitInstallSessionStatus.DOWNLOADING -> {
// You can use this method to update a progress indicator, for example:
// progressBar.max = state.totalBytesToDownload().toInt()
// progressBar.progress = state.bytesDownloaded().toInt()
}
SplitInstallSessionStatus.INSTALLING -> {
// Download complete; Installing...
// progressBar.progress = state.bytesDownloaded().toInt()
}
SplitInstallSessionStatus.INSTALLED -> {
splitInstallListener?.let {
splitInstallManager.unregisterListener(it)
}
onIncodeSdkInstalled()
}
SplitInstallSessionStatus.FAILED -> {
splitInstallListener?.let {
splitInstallManager.unregisterListener(it)
}
// Install failed
}
else -> {
Log.d(TAG, "Unhandled state ${state.status()}")
}
}
}.also {
splitInstallManager.registerListener(it)
splitInstallManager.startInstall(request)
}
}
}
private fun onIncodeSdkInstalled() {
// Everything is ready; You can start the Incode SDK now
}
companion object {
private const val REQUEST_CODE_CONFIRM_MODULE_DOWNLOAD = 0x8A7 // Use your own value
}
Step 9 - Testing your implementation​
If you now try to run your application from Android Studio, the Dynamic Module will be installed together with the app, and the code for downloading and installing the module will never execute.
To actually test your implementation (downloading and installing the Dynamic Module), you need to upload your App Bundle to Google Play.
To support on-demand modules, Google Play requires that you upload your application using the Android App Bundle format so that it can handle the on-demand requests from the server side.
Publishing the project on the Play Console requires some graphic assets. For testing purposes you can use these sample assets from the Google Codelab on Dynamic Features.
To be able to quickly test your application, without waiting for any approval, you can publish your application in the Internal Testing track.
For a step-by-step guide on how to publish a new application on the Google Play Store, you can follow the Play Console Guide on how to upload an app.
Helpful links for Dynamic Delivery​
Supporting both Google Mobile Services and Huawei Mobile Services​
Supporting both Google and Huawei Mobile Services can be achieved by using build flavors
and separate classes for downloading Dynamic Modules for GMS and HMS, which use the corresponding libraries.
A basic implementation is demonstrated in this example app:
Troubleshooting​
Attribute application@allowBackup value=(true) from AndroidManifest.xml​
Attribute application@allowBackup value=(true) from AndroidManifest.xml is also present at [com.incode.sdk:welcome:x.x.x] AndroidManifest.xml value=(false).
Suggestion: add 'tools:replace="android:allowBackup"' to `<application>` element at AndroidManifest.xml to override.
Solution​
This means that you have set allowBackup
in your AndroidManifest.xml
to true
, but the Welcome SDK has this value set to false
.
Having allowBackup
set to true
is a potential security issue, so we suggest setting it to false
.
If you are sure that you wish to allow backups, then add tools:replace="android:allowBackup"
to <application>
element in your AndroidManifest.xml
.
If you already have some attributes in tools:replace
, separate them with commas (for example: "android:label,android:allowBackup").
Static interface methods are only supported starting with Android N​
Static interface methods are only supported starting with Android N (--min-api 24): Lbutterknife/Unbinder;lambda$static$0()V
Error while dexing. The dependency contains Java 8 bytecode. Please enable desugaring by adding the following to build.gradle
android {
compileOptions {
sourceCompatibility 1.8
targetCompatibility 1.8
}}
Solution​
In your module-level [module]/build.gradle
, add the following code to android{}
closure:
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
All modules with native libraries must support the same set of ABIs​
Execution failed for task ':app:packageDebugBundle'.
> A failure occurred while executing com.android.build.gradle.internal.tasks.Workers$ActionFacade
> All modules with native libraries must support the same set of ABIs, but module 'base' supports '[X86, ARMEABI_V7A, ARM64_V8A, X86_64, MIPS, ARMEABI]' and module 'incode_core' supports '[ARMEABI_V7A, X86, X86_64, ARM64_V8A]'.
- problem when generating bundles.
Solution​
Add the following ndk abi filters to your module-level app/build.gradle
inside defaultConfig{}
closure:
ndk {
abiFilters "arm64-v8a", "armeabi-v7a", "x86", "x86_64"
}
FATAL EXCEPTION: RxCachedThreadScheduler​
E/AndroidRuntime: FATAL EXCEPTION: RxCachedThreadScheduler-2
Process: com.incode.welcome.example, PID: 6842
io.reactivex.exceptions.UndeliverableException: The exception could not be delivered to the consumer because it has already canceled/disposed the flow or the exception has nowhere to go to begin with. Further reading: https://github.com/ReactiveX/RxJava/wiki/What's-different-in-2.0#error-handling | java.lang.NoSuchMethodError: No virtual method log(ILjava/lang/String;Ljava/lang/Throwable;)V in class Lokhttp3/internal/platform/Platform; or its super classes (declaration of 'okhttp3.internal.platform.Platform' appears in /data/app/com.incode.welcome.example-HnCVs4pdPTZBq3BhFsJmdQ==/base.apk!classes3.dex)
at io.reactivex.plugins.RxJavaPlugins.onError(RxJavaPlugins.java:367)
at io.reactivex.internal.schedulers.ScheduledRunnable.run(ScheduledRunnable.java:69)
at io.reactivex.internal.schedulers.ScheduledRunnable.call(ScheduledRunnable.java:57)
at java.util.concurrent.FutureTask.run(FutureTask.java:266)
at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:301)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641)
at java.lang.Thread.run(Thread.java:919)
Caused by: java.lang.NoSuchMethodError: No virtual method log(ILjava/lang/String;Ljava/lang/Throwable;)V in class Lokhttp3/internal/platform/Platform; or its super classes (declaration of 'okhttp3.internal.platform.Platform' appears in /data/app/com.incode.welcome.example-HnCVs4pdPTZBq3BhFsJmdQ==/base.apk!classes3.dex)
at okhttp3.logging.HttpLoggingInterceptor$Logger$1.log(HttpLoggingInterceptor.java:114)
at okhttp3.logging.HttpLoggingInterceptor.intercept(HttpLoggingInterceptor.java:173)
at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.kt:100)
at okhttp3.internal.connection.RealCall.getResponseWithInterceptorChain$okhttp(RealCall.kt:197)
at okhttp3.internal.connection.RealCall.execute(RealCall.kt:148)
at retrofit2.OkHttpCall.execute(OkHttpCall.java:186)
at retrofit2.adapter.rxjava2.CallExecuteObservable.subscribeActual(CallExecuteObservable.java:45)
at io.reactivex.Observable.subscribe(Observable.java:12267)
at retrofit2.adapter.rxjava2.BodyObservable.subscribeActual(BodyObservable.java:34)
at io.reactivex.Observable.subscribe(Observable.java:12267)
at io.reactivex.internal.operators.observable.ObservableMap.subscribeActual(ObservableMap.java:32)
at io.reactivex.Observable.subscribe(Observable.java:12267)
at io.reactivex.internal.operators.observable.ObservableSubscribeOn$SubscribeTask.run(ObservableSubscribeOn.java:96)
at io.reactivex.Scheduler$DisposeTask.run(Scheduler.java:578)
at io.reactivex.internal.schedulers.ScheduledRunnable.run(ScheduledRunnable.java:66)
at io.reactivex.internal.schedulers.ScheduledRunnable.call(ScheduledRunnable.java:57)Â
at java.util.concurrent.FutureTask.run(FutureTask.java:266)Â
at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:301)Â
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167)Â
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641)Â
at java.lang.Thread.run(Thread.java:919)Â
Solution​
This crash occurs when the okhttp3:okhttp
dependency version is different from okhttp3:logging-interceptor
or okhttp3:okhttp-urlconnection
dependencies and trying to init Incode SDKs.
For example, in case you have in the Project's External Libraries
the following dependencies, you will face the crash because of the version inconsistency:
okhttp3:logging-interceptor:3.4.1
okhttp3:okhttp-urlconnection:3.4.1
okhttp3:okhttp:4.5.0
To fix the crash, for the example above, you need to bump the version of okhttp3:logging-interceptor
& okhttp3:okhttp-urlconnection
to 4.5.0
.
Add the following implementations inside build.gradle
to override existing lower versions:
dependencies {
...
implementation 'com.squareup.okhttp3:logging-interceptor:4.5.0'
implementation 'com.squareup.okhttp3:okhttp-urlconnection:4.5.0'
}
It is important to note that the crash will still occur in the case you only have the logging-interceptor
or okhttp-urlconnection
dependency.
Crashes if you have in External Libraries
these dependencies:
okhttp3:logging-interceptor:3.4.1
okhttp3:okhttp:4.5.0
Crashes as well if you have in External Libraries
these dependencies:
okhttp3:okhttp-urlconnection:3.4.1
okhttp3:okhttp:4.5.0
InvalidUserCodeException: Build was configured to prefer settings​
org.gradle.api.InvalidUserCodeException: Build was configured to prefer settings repositories over project repositories but repository 'maven' was added by build file 'build.gradle'
Solution​
For projects using Gradle 6.8 or higher, a new way of defining dependencies in settings.gradle
with the dependencyResolutionManagement
block was introduced. Moving the maven
repo declaration from the project build.gradle
to here should resolve it. See also StackOverflow.
dependencyResolutionManagement {
...
repositories {
...
maven {
url "https://repo.incode.com/artifactory/libs-incode-welcome"
credentials {
username = "organizationUsername"
password = "xxxxxxxxxxxxxxxxxxxx"
}
}
}
}
Fatal error during SentryAndroid.init(...)​
Since version 5.24.0, the Incode SDK includes the Sentry SDK for crash reporting.
We are currently using version 7.x.x, and if your project happens to be using a different major version (such as 6.x.x),
the following crash can occur:
java.lang.RuntimeException: Unable to get provider io.sentry.android.core.SentryInitProvider: java.lang.RuntimeException: Failed to initialize Sentry's SDK
...
Caused by: java.lang.RuntimeException: Failed to initialize Sentry's SDK
...
Caused by: java.lang.reflect.InvocationTargetException
...
Caused by: java.lang.NoSuchMethodError: No virtual method setEnableScopeSync(Z)V...
If you are affected by this error, you will need to upgrade to version 5.25.0 or newer, and apply the following workaround:
- Exclude the Sentry library from Incode SDK:
implementation ('com.incode.sdk:welcome:5.25.0') { // Mandatory dependency
exclude group: 'io.sentry', module: 'sentry'
}
- Disable Incode SDK's crash reporting:
- Java
- Kotlin
new IncodeWelcome.Builder()
...
.setCrashReportingEnabled(false)
.build();
IncodeWelcome.Builder()
...
.setCrashReportingEnabled(false)
.build()