Introduction
Dynamic Feature Modules (DFMs) in Android development are a powerful tool for modularising your app, reducing size, and enabling on-demand delivery. In this comprehensive guide, we’ll walk through everything from setting up your first DFM to solving common issues that developers often face.
3 parts of this blog series
- Part 1: Understanding the problem, solution approach & project setup (you are here)
- Part 2: Implementation of Dynamic Feature Module
- Part 3: Testing & troubleshooting of Dynamic Feature Module
Problem Statement
We know that, on average, 80% of users use only 20% of the features in an application. Suppose you have an application with 30 features (20 MB), and all of your features are in the app module. So, every user will download a 20 MB app, and it will occupy the required storage after installation. However, most of the features are not used by a user. For a specific user, it’s a waste of memory because they may only need 20–30% of the features.
What would it be like if we could download only the parts of our app that we needed? Consider an eCommerce application with a customer support feature that includes chat and audio call capabilities. To implement this feature, you have used a third-party SDK (e.g., 5 MB).
You know that this feature is not necessary for every user. So, the idea is to exclude the customer support feature from your main app module. When a user taps on the customer support button, they will be able to download this feature from the Play Store. After downloading, the user can use the feature.
Understanding Dynamic Feature Module
Generally, when we start Android development, we work in a monolithic app module. There is only one module in our application, and all features remain in this app module. We upload the .apk
or .aab
file to the Play Store, and users download our entire application at once.
On the other hand, in a dynamic feature module, we can split our application into a few modules. The user will download the base module (app module) for the first time. After that, they will be able to download other feature modules at runtime.
Example
Suppose you have an eCommerce application. Another feature is customer support (chat & audio-video call). Your main feature is online shopping (app module, for example, its size is 10 MB). However, for some special cases, users need to chat or call customer support. So, you can develop your customer support feature as a dynamic feature module outside the app module (for example, the customer support feature size is 5 MB).
The first time a user downloads the app, they will get the app module without the customer support feature. So, they will download a 10 MB app. When someone needs to contact customer support, they will click on your specific button and download the customer support module (size 5 MB) from the Play Store on-demand basis. This way, lots of users save an additional 5 MB cost.
That’s the idea of On-Demand Dynamic Feature Delivery Module!
Setup Dynamic Feature Module
First of all, I have created a new project with the App ID com.hellohasan.hasanerrafkhata. This app is already on the Play Store, and I have developer access on the Google Play Console. The App ID is important here for testing the dynamic feature module. You need to upload the .aab
file to the Google Play Store or use Google Play Internal App Sharing. So, make sure you have access to the Play Store for your mentioned Application ID.
After creating the project with my own app ID, I added dependencies in the build.gradle.kts
. In some tutorials, I found instructions to add the Google Play Core library. However, I believe it’s enough to add the feature-delivery library for this case.
build.gradle.kts (app module)
dependencies { // other dependencies implementation("com.google.android.play:feature-delivery:2.0.0") implementation("com.google.android.play:feature-delivery-ktx:2.0.0") }
After syncing the project, I encountered the first error!
FAILURE: Build failed with an exception. * What went wrong: Execution failed for task ':app:checkDebugDuplicateClasses'. > A failure occurred while executing com.android.build.gradle.internal.tasks.CheckDuplicatesRunnable > Duplicate class android.support.v4.app.INotificationSideChannel found in modules core-1.9.0-runtime (androidx.core:core:1.9.0) and support-compat-26.1.0-runtime (com.android.support:support-compat:26.1.0) Duplicate class android.support.v4.app.INotificationSideChannel$Stub found in modules core-1.9.0-runtime (androidx.core:core:1.9.0) and support-compat-26.1.0-runtime (com.android.support:support-compat:26.1.0) Duplicate class android.support.v4.app.INotificationSideChannel$Stub$Proxy found in modules core-1.9.0-runtime (androidx.core:core:1.9.0) and support-compat-26.1.0-runtime (com.android.support:support-compat:26.1.0) Duplicate class android.support.v4.os.IResultReceiver found in modules core-1.9.0-runtime (androidx.core:core:1.9.0) and support-compat-26.1.0-runtime (com.android.support:support-compat:26.1.0) Duplicate class android.support.v4.os.IResultReceiver$Stub found in modules core-1.9.0-runtime (androidx.core:core:1.9.0) and support-compat-26.1.0-runtime (com.android.support:support-compat:26.1.0) Duplicate class android.support.v4.os.IResultReceiver$Stub$Proxy found in modules core-1.9.0-runtime (androidx.core:core:1.9.0) and support-compat-26.1.0-runtime (com.android.support:support-compat:26.1.0) Duplicate class android.support.v4.os.ResultReceiver found in modules core-1.9.0-runtime (androidx.core:core:1.9.0) and support-compat-26.1.0-runtime (com.android.support:support-compat:26.1.0) Duplicate class android.support.v4.os.ResultReceiver$1 found in modules core-1.9.0-runtime (androidx.core:core:1.9.0) and support-compat-26.1.0-runtime (com.android.support:support-compat:26.1.0) Duplicate class android.support.v4.os.ResultReceiver$MyResultReceiver found in modules core-1.9.0-runtime (androidx.core:core:1.9.0) and support-compat-26.1.0-runtime (com.android.support:support-compat:26.1.0) Duplicate class android.support.v4.os.ResultReceiver$MyRunnable found in modules core-1.9.0-runtime (androidx.core:core:1.9.0) and support-compat-26.1.0-runtime (com.android.support:support-compat:26.1.0) Go to the documentation to learn how to <a href="d.android.com/r/tools/classpath-sync-errors">Fix dependency resolution errors</a>. * Try: > Run with --stacktrace option to get the stack trace. > Run with --info or --debug option to get more log output. > Run with --scan to get full insights. * Get more help at https://help.gradle.org BUILD FAILED in 9s
To solve this error, I added these two lines in the gradle.properties file.
android.useAndroidX=true android.enableJetifier=true
Create Dynamic Feature Module
Create a new module like below:
In the above configuration, I have set ‘Do not include module at Install-time (on-demand only)’ because I want to install the dynamic module on demand only. So, my dynamic module will not be included at install time. After creating the translation dynamic module, my project structure looks like the one below:
Then, I created a new Activity, CustomerSupportActivity.kt
, inside the customer_support
module. Android Studio internally changed some configurations in a few different places. If you want to convert any existing feature module into an on-demand dynamic module, you have to configure these manually. Let’s check them!
Changes in build.gradle.kts (app level)
android { // other configurations // this line is automatically added by Android Studio // after creating a new dynamic module dynamicFeatures += setOf(":customer_support") }
AndroidManifest.xml (dynamic module)
<dist:module dist:instant="false" dist:title="@string/title_customer_support"> <dist:delivery> <dist:on-demand /> </dist:delivery> <dist:fusing dist:include="false" /> </dist:module>
build.gradle.kts (dynamic module)
plugins { id("com.android.dynamic-feature") id("org.jetbrains.kotlin.android") } // others configurations dependencies { implementation(project(":app")) // other dependencies }
build.gradle.kts (Project level gradle)
// Top-level build file where you can add configuration options common to all sub-projects/modules. plugins { id("com.android.application") version "8.1.4" apply false id("org.jetbrains.kotlin.android") version "1.8.10" apply false id("com.android.dynamic-feature") version "8.1.4" apply false }
settings.gradle.kts
include(":app") include(":customer_support")
So, we are done with the basic setup part. In next blog we will implement the main functionalities of Dynamic Feature Module. Please check here.
Enjoy!
3 thoughts on “Mastering Android Dynamic Feature Delivery (1/3)”