If you want to keep your app download size small, you can use Google Play Feature Delivery to download the Incode Android SDK on demand, at runtime, as a Dynamic Feature Module. You can then uninstall the module when it's no longer needed. This page walks through the full setup: creating the Dynamic Module, configuring Gradle, installing at runtime, and testing. Dynamic Delivery is available for both Google and Huawei devices.
Warning
You cannot isolate the whole SDK
It is not possible to isolate the whole SDK into a dynamic module. The main, smaller part needs to be declared as a direct dependency. The core part, which contains larger files, can be separated into a dynamic module.
Requirements
- You must use the Android App Bundle format when publishing your app.
- Dynamic Feature Modules are supported on devices running Android 5.0 (API Level 21) or later.
Choose Your Mobile Services Path
Google Play Feature Delivery requires Google Mobile Services (GMS). For Huawei devices without GMS, use Huawei Dynamic Ability with Huawei Mobile Services (HMS).
| Your Target Devices | Use | Implementation |
|---|---|---|
| Have Google Mobile Services (GMS) | Google Play Feature Delivery | The step-by-step flow on this page. |
| Are Huawei devices without GMS | Huawei Dynamic Ability (Huawei Mobile Services, HMS) | Integration is very similar to Google's; classes and methods are named similarly. |
| Need to support both | Build flavors and per-service install classes | See Supporting both GMS and HMS below. |
Set Up Dynamic Delivery
Complete the following steps in order.
Add the Repository and Dependencies
- In your project-level
build.gradle, add the GitHub Packages repository with the provided credentials:allprojects { repositories { ... maven { url "https://maven.pkg.github.com/Incode-Technologies-Example-Repos/android-omni-packages" credentials { username = "incode-customers" password = "GITHUB_TOKEN" } } } ... }allprojects { repositories { ... maven { url = uri("https://maven.pkg.github.com/Incode-Technologies-Example-Repos/android-omni-packages") credentials { username = "incode-customers" password = "GITHUB_TOKEN" } } } ... } - In your module-level
app/build.gradle, add the following to theandroid{}closure:compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 }compileOptions { sourceCompatibility = JavaVersion.VERSION_1_8 targetCompatibility = JavaVersion.VERSION_1_8 } - Still in
app/build.gradle, add the 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.48.0' }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.48.0") }
Enable SplitCompat in Your Application
If you have an Application class, make it extend 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 don't 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"
...
>
Create the Dynamic Module in Android Studio
- In Android Studio, go to File > New > New Module.
- Select Dynamic Feature Module.
- Click Next.
- Enter a Module Name. For example,
incode_core. - Click Next.
- Enter a 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.
Review the Changes Android Studio Made
Wait for Android Studio to finish syncing, then review the following changes:
- A new module named
incode_corehas been added. - In
app/build.gradle, the following line has been added to theandroid{}closure:dynamicFeatures = [':incode_core']
- In
app/res/values/strings.xml, the following 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.
Add the Module Name String Resource
Open app/res/values/strings.xml and 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 must match the Module Name you configured previously.
Configure the Dynamic Module's build.gradle
- Open
incode_core/build.gradleand add the following to theandroid{}closure:compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 }compileOptions { sourceCompatibility = JavaVersion.VERSION_1_8 targetCompatibility = JavaVersion.VERSION_1_8 }
- Add dependencies as described.
Info
Note
The incode_core module does not need to contain any other code.
Add NDK ABI Filters
In your module-level [module]/build.gradle, add the following NDK ABI filters to the defaultConfig{} closure:
ndk {
abiFilters "arm64-v8a", "armeabi-v7a", "x86", "x86_64"
}
ndk {
abiFilters.addAll(listOf("arm64-v8a", "armeabi-v7a", "x86", "x86_64"))
}
The Gradle configuration is now complete.
Install the Module at Runtime
Before using the Incode Android SDK, make sure that the Incode Core Dynamic Module is installed. If it isn't, use the Google Play Core library to download and install it.
You can use this example code 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.module_name_incode_core)
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);
}
}
Test Your Implementation
Warning
Warning
If you run your application from Android Studio, the Dynamic Module is installed together with the app, and the code for downloading and installing the module never executes.
To test downloading and installing the Dynamic Module, upload your App Bundle to Google Play. Google Play requires the Android App Bundle format to handle on-demand requests.
Publishing the project on the Play Console requires graphic assets. For testing, you can use the sample assets from the Google Codelab on Dynamic Features.
To test quickly without waiting for approval, publish in the Internal Testing track.
For a step-by-step guide, follow the Play Console Guide on how to upload an app.
Support Both Google Mobile Services and Huawei Mobile Services
You can support both GMS and HMS using build flavors and separate classes for downloading Dynamic Modules, one for each service.
Helpful Links
- Google Dynamic Delivery
- Huawei Dynamic Ability
- Huawei Dynamic Ability Codelab (log in with your Huawei developer credentials to access)
See API Reference for full KDoc/Javadoc, or contact Incode support if you run into integration issues.