# Dynamic Delivery

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.

<Callout icon="🚧" theme="warn">
  ### 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.
</Callout>

***

## 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](#supporting-both-google-mobile-services-and-huawei-mobile-services) below. |

***

## Set Up Dynamic Delivery

Complete the following steps in order.

### Add the Repository and Dependencies

1. In your project-level `build.gradle`, add the GitHub Packages repository with the provided credentials:
   ```groovy
   allprojects {
       repositories {
           ...
           maven {
               url "https://maven.pkg.github.com/Incode-Technologies-Example-Repos/android-omni-packages"
               credentials {
                   username = "incode-customers"
                   password = "GITHUB_TOKEN"
               }
           }
       }
       ...
   }
   ```
   ```kotlin
   allprojects {
       repositories {
           ...
           maven {
               url = uri("https://maven.pkg.github.com/Incode-Technologies-Example-Repos/android-omni-packages")
               credentials {
                   username = "incode-customers"
                   password = "GITHUB_TOKEN"
               }
           }
       }
       ...
   }
   ```
2. In your module-level `app/build.gradle`, add the following to the `android{}` closure:
   ```groovy
   compileOptions {
       sourceCompatibility JavaVersion.VERSION_1_8
       targetCompatibility JavaVersion.VERSION_1_8
   }
   ```
   ```kotlin
   compileOptions {
       sourceCompatibility =  JavaVersion.VERSION_1_8
       targetCompatibility = JavaVersion.VERSION_1_8
   }
   ```
3. Still in `app/build.gradle`, add the Incode library dependencies:
   ```groovy
   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'
   }
   ```
   ```kotlin
   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`:

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

class BaseApplication : SplitCompatApplication() {
    // ...
}
```
```java
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`:

```xml
<application
    android:name="com.google.android.play.core.splitcompat.SplitCompatApplication"
    ...
    >
```

### Create the Dynamic Module in Android Studio

1. &#x20;In Android Studio, go to **File** > **New** > **New Module**.
2. Select **Dynamic Feature Module**.
3. Click **Next**.
4. Enter a **Module Name**. For example, `incode_core`.
5. Click **Next**.
6. Enter a **Module Title**. For example, `Incode Core`.
7. Set **Install-Time Inclusion** to _Do not include module at install-time (on-demand only)_.
8. Enable **Fusing**.
9. 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_core` has been added.
- In `app/build.gradle`, the following line has been added to the `android{}` closure:
  ```
  dynamicFeatures = [':incode_core']
  ```

* In `app/res/values/strings.xml`, the following string has been added:
  ```xml
  <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:

```xml
<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](#create-the-dynamic-module-in-android-studio).

### Configure the Dynamic Module's build.gradle

1. Open `incode_core/build.gradle` and add the following to the `android{}` closure:
   ```groovy
   compileOptions {
       sourceCompatibility JavaVersion.VERSION_1_8
       targetCompatibility JavaVersion.VERSION_1_8
   }
   ```
   ```kotlin
   compileOptions {
       sourceCompatibility = JavaVersion.VERSION_1_8
       targetCompatibility = JavaVersion.VERSION_1_8
   }
   ```

2) Add dependencies as [described](https://developer.incode.com/docs/android-installation#declare-dependencies).

<Callout icon="📘" theme="info">
  ### Note

  The `incode_core` module does not need to contain any other code.
</Callout>

### Add NDK ABI Filters

In your module-level `[module]/build.gradle`, add the following NDK ABI filters to the `defaultConfig{}` closure:

```groovy build.gradle (Module)
ndk {
    abiFilters "arm64-v8a", "armeabi-v7a", "x86", "x86_64"
}
```
```kotlin build.gradle.kts (Module)
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:

```kotlin
    // ...
    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
    }
```
```java
    // ...
    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

<Callout icon="🚧" theme="warn">
  ### 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.
</Callout>

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](https://github.com/googlecodelabs/android-dynamic-features/tree/master/graphic_assets) 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](https://support.google.com/googleplay/android-developer/answer/9859152).

***

## 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](https://developer.android.com/codelabs/on-demand-dynamic-delivery)
- [Huawei Dynamic Ability](https://developer.huawei.com/consumer/en/training/course/video/C101641376082448202)
- [Huawei Dynamic Ability Codelab](https://developer.huawei.com/consumer/en/codelab/DynamicAbility/index.html) (log in with your Huawei developer credentials to access)

***

See [API Reference](https://developer.incode.com/docs/android-api-reference) for full KDoc/Javadoc, or contact Incode support if you run into integration issues.
