On Demand Delivery aka Dynamic Delivery

On Demand Delivery aka Dynamic Delivery

Make feature delivery more dynamic and simple.

What's dynamic delivery?

Nowadays application size is increasing rapidly due to the latest feature application and modern technologies which is highly dependent on libraries which can easily increase the size of the app. Dynamic delivery is a way of delivering those features or resources which is not immediately needed by the users. But when they start using that feature which uses that module dynamic delivery helps to download that specific module and resources on demand when they ask. Thus application size gets radically decreased and the app becomes light.

Suppose your application has an app module which is the default. Now you will have three other feature module

  • Feature A, 10MB

  • Feature B, 20MB

  • Feature C, 30MB

Which is all on demand and will not be needed for the basic usage of the application. Now by default practice, it will add an extra 60 MB to the application size. But if we use dynamic delivery we can tell the play console and app not to include these at first but when the user starts using the feature, a prompt will be shown and ask user if they want to use it. By continuing this feature will be downloaded and the user will able to use the feature.

Implementation Process

Its is very easy to implement but there's are some tricks what you need to play which are will be covered in this article but not even in the official documentation. So make sure you read it to the end!!!!

Add the dependencies:

In app/build.gradle,

implementation "com.google.android.play:core:${versions.playcore}"

So that the complete dependency, in the main module gradle.build file,

implementation "com.google.android.play:core:${versions.playcore}"
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:${versions.kotlin}"

Convert the module to on demand module:

There are a couple of steps to convert the asset library to be an on-demand module:

  1. This is the tag to add to the AndroidManifest.xml of the asset module,
<dist:module dist:title="@string/module_assets">
    <dist:delivery>
        <dist:on-demand/>
    </dist:delivery>
    <dist:fusing dist:include="true" />
</dist:module>
  • dist:title: Specify a user-facing title for the module. The platform uses this title to identify the module to users when, for example, confirming whether the user wants to download the module.

  • dist:onDemand Specify that you want to have the module available as on-demand delivery. If you do not enable this option, the dynamic feature is available when a user first downloads and installs your app.

  • dist:fusing: Specify if you want this module to be available to devices running KitKat and lower and include it in multi-APKs (when dist:include is set to true).

  1. Add the build flavors in your feature gradle. Only the flavors in your application build.gradle has. If there is no build flavor, this code will not be needed. Suppose your have has 3 build flavor dev and live. So you have to put these two build flavor in on demand module build.gradle.
flavorDimensions += "version"
namespace = "package name"
productFlavors {
    create("dev") {
    }

    create("live") {
    }
}
  1. This is one of the most important part, which is excluding jetbrains annotation in the on demand module build.gradle, which can create conflicts among other dependencies. Because some of the libraries used by your project can also already implemented jetbrains annotations so it may cause conflicts which is not clear by the compiler and will give a unrelated error message.
android{}
configurations.implementation {
    exclude(group = "org.jetbrains", module = "annotations")
}

Now add the on demand module to the project

Suppose we are adding dynamic on demand module in the Feature directory. To support it throughout the application we have to add some lines in our projects,

  1. Include it on the settings.gradle file,

     include(":feature")
     include(":features:featureA")
    
  2. Add the following ndk keyword in the app build.gradle file of your application,

     ndk {
         abiFilters.add("arm64-v8a")
         abiFilters.add("x86_64")
         abiFilters.add("x86")
         abiFilters.add("armeabi-v7a")
     }
    
    • By specifying these ABIs, you are indicating which architectures your native code should be compiled for. This is important because different Android devices may have different architectures, and your app needs to provide the appropriate native libraries for each.

    • This configuration ensures that your app includes native libraries optimized for the specified ABIs, making it compatible with a broader range of Android devices.

  3. Comment out the following line in build types if you have, because sometime it can cause unreported errors,

     //            applicationIdSuffix = ".debug"
    

The line applicationIdSuffix = ".debug" is typically used to append a suffix to the application ID during the build process. It can be useful for distinguishing between different build variants, such as debug and release versions of your application.

However, when implementing on-demand delivery with dynamic feature modules in Android, you might encounter issues related to the applicationIdSuffix. Commenting out this line is recommended in some cases, and here's the rationale:

  1. Module Installation:

    • On-demand modules are installed dynamically when needed. If your app has different application IDs for debug and release variants, it may lead to complications when trying to install on-demand modules.
  2. Module Identification:

    • On-demand modules are identified by their module name, not by the application ID. Using applicationIdSuffix may lead to confusion when checking for the availability or status of on-demand modules.
  3. Dynamic Features and Application IDs:

    • The Play Core Library, which is used for on-demand delivery, manages modules independently of the application ID. Therefore, using applicationIdSuffix may not be necessary or may cause unexpected behavior.

Congratulations, now it should run your project without any error. It is the module setup process for on demand module creation.

Implementation and Downloading the module

First of all, in the application class attach SplitCompat in with application in the attachBaseContext method. You can also do it in onCreate.

@Override
protected void attachBaseContext(Context base) {
    super.attachBaseContext(LocaleHelper.onAttach(base));
    SplitCompat.install(this);
}

Start creating an instance of SplitInstallManager in the Activity using the static create() method of SplitInstallManagerFactory class:

private lateinit var manager: SplitInstallManager

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)
    manager = SplitInstallManagerFactory.create(this)
}

Now with this method you can download the module and do operations based on the status of download,

fun downloadDynamicModule(moduleName: String) {
    val request = SplitInstallRequest.newBuilder()
        .addModule(moduleName)
        .build()

    val listener =
        SplitInstallStateUpdatedListener { splitInstallSessionState ->
            if (splitInstallSessionState.sessionId() == mySessionId) {
                when (splitInstallSessionState.status()) {
                    SplitInstallSessionStatus.INSTALLED -> {
                        // module is installed
                    }

                    SplitInstallSessionStatus.DOWNLOADING -> {
                        // module is downloading
                    }

                    SplitInstallSessionStatus.DOWNLOADED -> {
                        // module downloaded successfully
                    }

                    SplitInstallSessionStatus.FAILED -> {
                        // module download failed
                    }
                }
            }
        }

    splitInstallManager.registerListener(listener)

    splitInstallManager.startInstall(request)
        .addOnFailureListener { e ->
          // installlation failed
        }
        .addOnSuccessListener {
          // installation success
        }
}

Here we are using using Split Install Manager which allows us to check if an on-demand module is installed, and avoid crashes - like the one we saw before - if we try to access a module that is not installed.

This code, when invoked, initiates the download and installation of the specified on-demand module, and the provided listener handles different states of the installation process. It's a crucial part of implementing dynamic delivery and on-demand module loading in Android applications.

Inside the listener, a when statement is used to handle different states of the installation process:

  • INSTALLED: The on-demand module is installed successfully.

  • DOWNLOADING: The module is currently being downloaded.

  • DOWNLOADED: The module is downloaded successfully and ready for use.

  • FAILED: The module download has failed.

Now to check if the module is already downloaded or not simply use this method,

fun isModuleDownloaded(moduleName: String): Boolean {
    return splitInstallManager.installedModules.contains(moduleName)
}

Here split install manager checks if the asking module is available in installed module or not. If installed it will return true else false.

Conclusion

On-demand modules are crucial for optimizing resource management and reducing application sizes. The Split Install Manager is a key tool for managing the installation and status of on-demand modules.

Your detailed guide, including the implementation steps and code snippets, makes it easier for developers to understand and implement dynamic delivery in their Android applications. If readers encounter any issues, you encourage them to seek assistance, providing a helpful resource for troubleshooting.

Overall, this comprehensive explanation should be beneficial for Android developers looking to implement dynamic delivery and on-demand module loading in their applications.