Android Dynamic Feature Module (DFM)

dynamic_feature.png?w=2924

What’s a DFM?

Speaking of Dynamic Feature Module, we need to mention a little bit about the Android Application Bundle (*.aab) format first.

An Android App Bundle is a new upload format that includes all your app’s compiled code and resources, but defers APK generation and signing to Google Play.

Google Play’s new app serving model, called Dynamic Delivery, then uses your app bundle to generate and serve optimized APKs for each user’s device configuration, so they download only the code and resources they need to run your app. You no longer have to build, sign, and manage multiple APKs to support different devices, and users get smaller, more optimized downloads.

– from Google Developer

The most significant benefit of the aab format is to allow Google play store to send different feature combinations and different resource settings of your App to various user devices according to the device hardware spec. That helps to reduce the download time. Why? if users want to use one or two features from your App, they can download the parts they want or the parts they are interested in instead of downloading full feature-set App at one time. This way allows us to decrease the APK size, eliminate the downloading time[1], and increase the App download rate/retention rate.

Let’s see how it works.

aab_format-2x.png

You can see the above architecture: developers should separate their features into different feature modules. In this diagram, it has one base module and two different feature modules. The Google play store controls the process of generating different APK packages for users. When a new user tries to download your App. It will process and form a new suitable version to this user like this:

aab.gif
Dynamic Delivery

Here list out the advantages for Android Application Bundle.

  • Develop in parallel: By separating logical components of your app into modules, different teams or individuals in your organization can take ownership of each module and work on them with fewer merge conflicts or disruptions to other groups. Additionally, if you have logic included in various parts of your app, you can use library modules to promote code reuse and encapsulation.
  • Improve build times: Build systems, such as the Android Studio build system using Gradle, are optimized for projects that are using modules. For example, if you enable Gradle’s parallel project execution optimization on a workstation that includes a multi-core processor, the build system can build multiple modules in parallel and significantly reduce build times. The more modular your project is, the more significant the build performance improvement becomes.
  • Customize feature delivery: Modularizing your app’s features as dynamic feature modules is a requirement to take advantage of Dynamic Delivery’s custom delivery options, such as on-demand, conditional, and instant delivery. Creating on-demand dynamic features requires more effort and possible refactoring of your app. So, consider carefully which of your app’s features would benefit the most from being modularized into dynamic features and benefiting from custom delivery options.

Then, here is a crucial point: you need to create your dynamic feature modules first. Let’s do together.

Step 1 - Create a new module

dynamic_feature_module.png
Choose feature module

Remember, there’re several module choices here. We need to select the dynamic feature module type.

dynamic_feature_module_info.png
Input basic information

Input related information for your module, for example, module name, package path, etc.

dynamic_feature_module_properties.png
Choose properties

Two things need to mention here.

  1. On-Demand Specifies whether the module should be available as an on-demand download. That is if this attribute sets to true, the module is not available at install time, but your app may request to download it later. If this attribute sets to false, the dynamic feature module is included when the user first downloads and installs your app.
  2. Fusing: Specifies whether to include the module in multi-APKs that target devices are running Android 4.4 (API level 20) and lower. Note: only dynamic feature modules that set this property to true are included in the universal APK when you use bundleTool to generate APKs.

So far, you should see a feature module has been added to your project. But it’s sometimes not so easy to create a feature module. You might see the following errors.

dynamic_feature_module_errors.png
Feature module errors

Then, what’s going on? Here’s the reason. In your base module, you might use the productFlavors to define different build variants.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
... 

flavorDimensions "domestic"
productFlavors {
area1 {
applicationIdSuffix ".area1"
dimension "domestic"
}
area2 {
dimension "domestic"
}
}

dynamicFeatures = [":dynamic_feature"]
...

All you need to do is to set up the same productFlavors into your dynamic feature module. Then the problem has been solved.

Ok, then what? How do we download the dynamic feature and track current status? Here you go:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
private fun manageFeatureModules() {
val manager = SplitInstallManagerFactory.create(this)
val request =
SplitInstallRequest.newBuilder()
.addModule("dynamic_feature")
.build()

var sessionId = 0
val listener = object: SplitInstallStateUpdatedListener {
override fun onStateUpdate(state: SplitInstallSessionState?) {
if (state.sessionId() == sessionId) {
when (state.status()) {
// handle error case
SplitInstallSessionStatus.FAILED -> {
when (state.errorCode()) {
// handle different error codes
SplitInstallErrorCode.SERVICE_DIED -> {}
SplitInstallErrorCode.NETWORK_ERROR -> {}
}
}
// handle downloading status
SplitInstallSessionStatus.DOWNLOADING -> {}

// handle downloaded status
SplitInstallSessionStatus.DOWNLOADED -> {}

// handle installing status
SplitInstallSessionStatus.INSTALLING -> {}

// handle installed status
SplitInstallSessionStatus.INSTALLED -> {}

// handle canceling status
SplitInstallSessionStatus.CANCELING -> {}

// handle canceled status
SplitInstallSessionStatus.CANCELED -> {}
}
}
}
}
// register listener
manager.registerListener(listener)
manager.startInstall(request)
.addOnSuccessListener { sessionId = it }
.addOnFailureListener { exception ->
exception.printStackTrace()
}

// unregister listener when you don't want to use it.
//manager.unregisterListener(listener)

// cancel installation by using your session ID
//manager.cancelInstall(sessionId)
}

Happy coding.

[Reference]

[References]

  1. What a new publishing format means for the future of Android
  2. Build your Android app Faster and Smaller than ever