Implementing Dynamic Feature Modules in our Android App

Lucky Senjaya Darmawan
julotech
Published in
6 min readMar 27, 2023

--

App Size vs. Install Conversion Rate

When users want to download an android app from the play store, one of the indicators that contributes to user decision is app size. According to these articles, there is correlation between app size and negative install conversion rate:

For every 6 MB increase to an APK’s size, we see a decrease in the install conversion rate of 1%.” —

We found that the download completion rate of an app with an APK size of around 10 MB will be ~30% higher than an app with an APK size of 100MB” —

The solution for this problem is obviously to reduce the app size. There are many ways to reduce the app size, such as using vector graphics, reusing resources, compressing image files, etc.

While we can do these methods, another way to to significantly reduce the app size is to utilize the Play Feature Delivery from Google Play. It allows us to install certain app features on demand. To utilize this method of delivery, we need to create a Dynamic Feature Module (DFM), also sometimes known as On-demand Module.

What are Dynamic Feature Modules?

Dynamic feature modules are modules for separating features and resources from the base APK. Users can later download and install these modules on demand after they’ve already installed the app from Google Play. For example, we may have certain features that only a small percentage of users can access. For this, we can make the feature delivered to users only when they need it. Read more about this from the official source.

Motivation

We at JULO have experimented with DFM because our APK size kept growing in recent years and this contributed negatively to our install conversion rate.

When we looked at our app, we realized that it has several features that only can be accessed by users at a later time, so it makes sense to not include it in the base APK. We also have a big module that contributes heavily to our app size and we are planning to put it on dynamic delivery.

Research And Prerequisites

As we research there are two requirements to utilize this feature, which are:

  1. We needed to enable App Bundles, but fortunately we already have used it at this point.
  2. Modular Code Base. One of the challenges of our app is that there are several legacy features implemented inside our main big app module. Meaning, those features cannot be put yet into as DFMs because they need to be separate feature modules.

For context, JULO Android App stack looks something like this:

  • We already use Android App Bundles.
  • We have 1 app module and several modules that are separated by business features.
  • Our app implements MVVM with layers separating views, domains, and data.
  • We also use Dagger 2 for dependency injection.

So what we did before deploying DFMs are:

  • Refactor and modularize some of the features.
  • Encourage developers to put the new features in the new modules

We also had discussions with PMs and stakeholders to let them know the benefits of DFM and to hear their thoughts and inputs for the best user experience. One of the concerns was how reliable DFM is when the user has a slow internet connection and the modules can’t be downloaded. Surely we must handle that, so we decided to test it first with small modules to check its reliability.

Creating a Dynamic Feature Module

In this section, we are going to see how to create a DFM.

Setup

  • Add play:core library to app.gradle:
implementation "com.google.android.play:core:${versions.playcore}"

Create a new module by selecting Dynamic Feature Module in File > New > New Module

  • Configure the module name, package name, and minimum API level and click Next
  • Specify the module title and choose Do not include module at install-time to use DFM
  • Check the Fusing checkbox if your minimum SDK is pre-Lollipop devices. Pre-lollipop devices don’t support DFM and the modules will be included in base APK.

Request a Dynamic Feature Module

When we need to use a DFM, the app needs to request it first. There are two ways to request it.

  • Using SplitInstallManager.startInstall() to request modules on the foreground.
val request = SplitInstallRequest.newBuilder()
.addModule(getModuleName())
.build()

// Load and install the requested feature module.
val manager = SplitInstallManagerFactory.create(this)
manager.startInstall(request)

When we use this we can track the progress of the download manager by attaching the listener to the manager. We can show a loading UI to show the progress to the user.

private val listener = SplitInstallStateUpdatedListener { state ->
when (state.status()) {
SplitInstallSessionStatus.DOWNLOADING -> {
//show the loading progress
displayLoadingState(state)
}
SplitInstallSessionStatus.INSTALLED -> {
//action after the modules downloaded
onSuccessfulLoad()
}

SplitInstallSessionStatus.FAILED -> {
//add retry mechanism when the download fail
showRetryDialog()
}
}
}
manager.registerListener(listener)kot
  • Using SplitInstallManager.deferredInstall() to request modules in the background.
val request = SplitInstallRequest.newBuilder()
.addModule(getModuleName())
.build()

val manager = SplitInstallManagerFactory.create(this)

manager.deferredInstall(request)

Since the download happens in the background, we can’t track the progress of it when we use this. So before we open the modules, we need to check whether the module has been installed or not. If it hasn’t we can always install it on demand in the foreground.

//check whether the module has been installed or not
val modules = manager.getInstalledModules()
if(modules.contains(“dynamic-feature-module”){
//open the feature module
}
else{
//proceed with manager.startInstall(request)
}

Calling a Dynamic Feature Modules class

The code inside a DFM is not accessible from the base modules at compile time, but it’s available at runtime. This is because with DFM, the base class is a dependency of the dynamic feature module. You can launch an activity by using normal Intent but giving the full class name as a parameter. This is the example:

Intent().setClassName(“com.package.app”,“com.package.module.DFMActivity”)

Testing

Since DFM utilizes Google Play to deliver the modules, we can’t test it using the normal way. There are two ways to test it:

  1. Using Internal Test that available in Google Play
  2. Using Bundle Tool to generate an APK

To have a similar experience with the users, we decided to use the Internal App Sharing feature from Google Play. We only need to generate AAB from android studio and we can upload it here.

After uploading we can share the link to the tester and they can open it from any browser. The link will go to the Play Store and will open the application page.

Limitation & Considerations

  • According to Google, installing 50 or more features modules via on-demand delivery might lead to performance issues. We only implemented several modules so far, so no performance issue here.
  • When the requested module size is more than 10 MB, a user confirmation dialog will show up. Since we only implemented this in modules that are <10 MB, we don’t have to handle dialog displaying.
  • Make sure to always catch the error because the installation can sometimes fail. There are many possible reasons like low storage, network problems, or not having a Google Play account. We should show a proper error message for each case so the user won’t get stuck and can’t access the feature.
  • If possible, we may need to monitor the download progress and save the progress or the error that happens to the database so we can analyze and check how reliable this feature is. In collaboration with the backend team, we have implemented an API to store user-app interactions. Using this API, we can log progress or the errors to our backend to be analyzed further later.

Results

We have already implemented Dynamic Feature Modules on several of our business features. The result is we have already reduced the size by around 3 MB.

Conclusion

Implementing Dynamic Feature Modules is a great way to reduce app size. But there are several things we need to consider:

  • We must make sure our app is modular, especially for the features that we plan to use as dynamic modules.
  • We also need to have discussions with stakeholders to hear their concerns over this and get their inputs to ensure the best user experience when downloading dynamic modules.
  • There is a possibility of users getting stuck and can’t access the feature. We need to carefully address every error possibility and respond accordingly such as showing informative error messages.
  • If possible, monitor every progress or error and we can store it in our backend to be properly analyzed.

By taking these steps, we can successfully utilize Dynamic Feature Modules to deliver better, faster, and more efficient apps to our users.

References:

--

--