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.30.0'
}

Step 2

If you have an Application class, make it extend from SplitCompatApplication

import com.google.android.play.core.splitcompat.SplitCompatApplication

class BaseApplication : SplitCompatApplication() {
    // ...
}
import com.google.android.play.core.splitcompat.SplitCompatApplication;

public class BaseApplication extends 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 to Do 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 to android{} 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):

    // ...
    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
    }
    // ...
    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);
        }
    }

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.