Dynamic Feature Delivery in a Multi-Module Project

Rishvik Vardhan
4 min readJan 25, 2023

--

Overview of Dynamic Delivery

A unique benefit of dynamic feature modules is the ability to customize how and when different features of your app are downloaded onto devices running Android 5.0 (API level 21) or higher. For example, to reduce the initial download size of your app, you can configure certain features to be either downloaded as needed on demand or only by devices that support certain capabilities, such as the ability to take pictures or support augmented reality features.

You can also refer to this sample project on dynamic feature modules
https://github.com/S-Rishvik/DynamicFeature

Creating a new dynamic feature module

Dynamic feature modules are just like any other modules in Android. To add a dynamic feature module to your app project using Android Studio, proceed as follows:

  1. Select File > New > New Module from the menu bar. In the Create New Module dialog, select Dynamic Feature Module and click Next.
  2. In the dropdown menu under Install-time inclusion, select delivery type and click Finish.

Let’s learn more about delivery options later in this article.

Converting an existing module to a dynamic feature module

In most cases, we don’t want to create a new dynamic feature module, but instead we would like to convert an already existing module into a dynamic feature module.

In app’s build.gradle file

  1. Add the android.dynamicFeatures property as follows
android {
...
// Specifies feature modules that have a dependency on
// this base module.
dynamicFeatures = [':installTime',':onDemand']
}

2. Include play feature delivery library

api 'com.google.android.play:feature-delivery-ktx:2.0.1'

Use api as dynamic feature modules depend on app module

In dynamic feature's build.gradle file

  1. Replace plugin com.android.library
plugins {
// Replace id com.android.library
id 'com.android.dynamic-feature'
}

2. Specify additional ProGuard rules

android.buildTypes {
release {
// You must use the following property to specify additional ProGuard
// rules for feature modules.
proguardFiles 'proguard-rules-dynamic-features.pro'
}
}

3. Dynamic feature modules have a reverse dependency, i.e., dynamic feature modules depend on app module. Add app module as a dependency of the feature module.

dependencies {
...
// Declares a dependency on the base module, ':app'.
implementation project(':app')
}

4. You can specify the delivery option from the dynamic feature module’s manifest. There are three types of delivery options :

Install Time Delivery : Downloading at app install

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:dist="http://schemas.android.com/apk/distribution" >

<dist:module
dist:instant="false"
dist:title="@string/title_installtime" >
<dist:delivery>
<dist:install-time>
<!-- Install-time modules are not removable by default.
To mark a module as removable and allow it to be uninstalled, add the removable tag and set its value to true -->
<dist:removable dist:value="true"/>
</dist:install-time>
</dist:delivery>

<dist:fusing dist:include="true" />
</dist:module>
.
.
.
</manifest>

On Demand Delivery : On demand download

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:dist="http://schemas.android.com/apk/distribution">

<dist:module
dist:instant="false"
dist:onDemand="true"
dist:title="@string/title_on_demand">
<dist:fusing dist:include="true" />
</dist:module>
.
.
.

</manifest>

Conditional Delivery : Allows you to set certain device configuration requirements for feature modules to be downloaded automatically during app install.

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:dist="http://schemas.android.com/apk/distribution">

<dist:module
dist:title="@string/feature_title">
<dist:delivery>
<dist:on-demand/>
<dist:install-time>
<dist:conditions>
<!-- Requires that the device support AR to download the module at
app install-time. -->
<dist:device-feature dist:name="android.hardware.camera.ar"/>
</dist:conditions>
</dist:install-time>
</dist:delivery>
<dist:fusing dist:include="true"/>
</dist:module>
.
.
.

</manifest>

Specify the Module title using up to 50 characters in app’s base module string resource.

<resources>
<string name="app_name">Dynamic Delivery</string>
<string name="title_installtime">Install Time Delivery</string>
<string name="title_on_demand">On Demand Delivery</string>
.
.
.
</resources>

In order to access the code and resources from the downloaded module, our application needs to enable the SplitCompat Library for the app and each entry point (Activities or Fragments) in the downloaded Dynamic Feature module.

class DynamicDeliveryApplication: SplitCompatApplication() {
override fun attachBaseContext(base: Context?) {
super.attachBaseContext(base)
if (base != null) {
SplitCompat.install(base)
}
}
}

Also mention this application class in manifest

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">

<application
android:name=".DynamicDeliveryApplication"
.
.
.
</application>
</manifest>

Enable the SplitCompat Library in the Activity/Fragment entry point inside the Dynamic Feature module

class OnDemandActivity : ComponentActivity() {

override fun attachBaseContext(newBase: Context?) {
super.attachBaseContext(newBase)
if (newBase != null) {
SplitCompat.installActivity(newBase)
}
}
.
.
.
}

That’s it. Now you have converted your module to a dynamic feature module.

Request an on demand module:

Initialize SplitInstallManager in the app module’s activity/fragment.

private val splitInstallManager: SplitInstallManager by lazy {
SplitInstallManagerFactory.create(applicationContext)
}

On Demand Module installation

private fun requestModuleInstallation(moduleName: String, activityName: String) {
val request = SplitInstallRequest.newBuilder().addModule(moduleName).build()
splitInstallManager.startInstall(request).addOnFailureListener {
_dynamicDeliveryLiveData.value = Failed
}
getDynamicModuleStatus(moduleName, activityName)
}

Monitoring the request state and updating the UI using Livedata

import com.google.android.play.core.splitinstall.model.SplitInstallSessionStatus.*

private fun getDynamicModuleStatus(moduleName: String, activityName: String) {
manager.registerListener {
_dynamicDeliveryLiveData.value = when (it.status()) {
CANCELED -> Available
DOWNLOADING -> Installing(
((it.bytesDownloaded().toDouble() / it.totalBytesToDownload())*100).toInt()
)
DOWNLOADED -> Installing(100)
FAILED -> Failed
INSTALLED -> Installed(activityName)
REQUIRES_USER_CONFIRMATION -> NeedsConfirmation(it)
CANCELING, PENDING -> Installing(0)
else -> Unavailable
}
}
}

You can request the device to uninstall modules by invoking

// Specifies two feature modules for deferred uninstall.
splitInstallManager.deferredUninstall(listOf("installTime", "onDemand"))

Module uninstalls do not occur immediately. That is, the device uninstalls them in the background as needed to save storage space.

Hurray! You have accomplished it.

In the next part you will learn how to test dynamic feature modules locally.

--

--