Chapters

Hide chapters

Android App Distribution

First Edition · Android 12 · Kotlin 1.5 · Android Studio Bumblebee

8. Adding Features Dynamically
Written by Evana Margain Puig

With AABs, or Android App Bundles, Android lets you deliver features dynamically. In this context, dynamically means several possibilities. For instance, users can download only the parts of the app they need, or you can prompt them to download specific information if they access a specific part of your app.

Besides delivering features dynamically through your AAB, you can use another technique, called Feature Flagging. This term refers to conditional statements, controlled either by the server or the code itself, which tell the app whether to show or hide specific features.

What are dynamic features?

You’ll start this chapter by learning how to deliver features through a tool called Play Feature Delivery. This term refers to the features you can deliver after the user downloads the app on a per-need basis or any criteria you or your team determine.

Why are dynamic features useful?

There are many reasons why you might want to deliver features dynamically. Take a moment to explore some examples:

  1. You want to reduce your app’s download size, either because of Google Play Store’s size limitations or because you want to help users with constrained amounts of data.
  2. You want to support a wide variety of devices, but you have certain features that need specific hardware. In that case, you can let devices with a particular capability to download a feature, while those without that capability keep the original functionality.
  3. You have users with certain ‘privileges’ that may have access to sections of your app that aren’t available to the rest of the user base.

Dynamic feature options

Android provides four delivery options:

  1. Install-time delivery: The device downloads these features on the app’s first launch. You often see this in mobile games: You get the game, and then on the first launch, after the splash, the app downloads several megabytes of data.
  2. On-demand delivery: This is the most common use case. On-demand is when users download a section not included in the original download as they need it.
  3. Conditional delivery: This option relates to the device’s capabilities, either at the hardware or API level. Users with the required capabilities get some extra features, while those without those capabilities get the app as-is.
  4. Instant features: This option lets you give users access to certain features without downloading your app. You’ll learn about this topic in detail later.

Feature flagging

Feature flagging isn’t an Android-specific term. It’s used in various applications and is more of a software development principle. Feature flagging gives you a way to enable or disable individual modules of your app depending on certain conditions. In this chapter, you’ll learn about this powerful tool.

Delivering dynamic modules

With basic definitions out of the way, it’s time to start really learning! In this section, you’ll add a series of dynamic modules to Pod Play and explore different delivery options.

First, you need to choose your base module. The base module should have this line of code in its build.gradle:

apply plugin: 'com.android.application'

If you created the project with Android Studio, this code is already there. To be sure, look at the gradle file on the module. Validate that the first line of PodPlay contains that line of code, as shown in the image below:

Now that you know that this is your app’s base module, there are a series of things you need to check:

  1. Make sure you include all the images and resources required by the base module in the initial APK. You can’t move them to another module.
  2. This module’s manifest will be the base manifest, and the APK will take data, like version name and version code, for the full app. This manifest is also where you specify your app’s entry point.
  3. Even though this module has the main manifest, the build process will merge all the other modules’ manifests into one file.

In a production app, you would look at the details of everything listed above to suit your needs. However, in PodPlay, this is mainly taken care of in the starter project.

Install-time delivery

You’ll start by creating an install-time delivery feature. There are three main reasons to choose this approach:

  1. Reducing the size of your initial APK.
  2. Modularizing your app from the beginning of development and later converting to on-demand modules. Otherwise, your app will require a huge refactor if you decide to do this later.
  3. Showing the user a one-time feature and then uninstalling it to reduce the space your app occupies. An onboarding module that the users ask not to see again is an example of a one-time feature.

Creating a new module

Open up the starter project. Then create your new module by clicking File ▸ New… ▸ New Module. The menu will look like this:

After clicking this, you’ll get a menu with the different modules you can create. Since you’re focusing on install-time delivery in this part of the chapter, select “Dynamic Feature”, as shown here:

The information you need to fill in will be specific to each of your apps, but explore the current example options:

  1. Base Application Module: In the case of PodPlay, you only have one base module, PodPlay.app. In other apps, you may have more than one. This option gives you control over which module is the parent of the new module you create.

  2. Module Name: You want to give your module a name so you can identify it in your project and throughout the areas where you’ll make configurations for it, in this case use InstallTimeDeliveryExample.

  3. Package Name: This is similar to the base app’s package name. It’ll automatically generate for you when you input Module Name, but you can click the edit button and change it yourself. The recommended practice uses reversed domain names, as you do when naming apps.

  4. Language: While it would be ideal to use the same language your project uses, it’s not necessary. For example, you can have an app written in Java code with a module written in Kotlin.

  5. Minimum SDK: Ideally, the minimum SDK would be the same as your app or a more recent version. Otherwise, there may be compatibility issues if your app supports a newer version than your module.

If you want to know how the final configuration should look, check out the image below:

After inputting all the data, click Next. Another window will appear, letting you configure your module’s behavior and the user-facing name. As in the previous screen, look at each of the items:

  1. Module title: As the field description mentions, users may see the module’s name. For example, they would see the name if the app requests permission to install the module. So, make this title as descriptive and straightforward as possible.
  2. Install-time inclusion: This item determines which type of module you create. For this first example, you’ll choose Include module at Install-Time, which means as soon as the user downloads the app, the app will download this module.

Click Finish, and wait for Android Studio to create your module. While it may not look like it’s doing anything, the status text at the bottom of the screen will tell you it’s working.

Once it creates the module, you’ll find it listed to the left of Android Studio, below your main module, as in the picture below:

Understanding your module configuration

The information you previously selected in the dialogs will mostly be in the newly created module’s Android Manifest. Open the Android Manifest for this new module. It is located in nameOfYourModule ▸ manifests ▸ AndroidManifest.xml. The three lines you need to pay attention to are:

  1. This isn’t an instant feature but an ‘installable’ module.
  2. Here’s the user-facing title. If you don’t like the title you chose in the assistant, you could change it here.
  3. This indicates the feature is an install-time feature.

Android Studio also modified build.gradle (:app) when creating the module. Open this file by going to Gradle Scripts ▸ build.gradle(Module: PodPlay.app). There, at the end of the curly brackets with the ‘android’ label, you’ll see the following line:

dynamicFeatures = [':installTimeDeliveryExample']

Note that dynamicFeatures is an array, so if you had more than one module, it would contain a comma-separated array of modules:

dynamicFeatures = [":module1", ":module2"].

Note: Android recommends having no more than ten install-time modules. Otherwise, the app install and download can take a long time and affect your users’ engagement.

Finally, take a look at the created module’s gradle file, Gradle Scripts ▸ build.gradle(Module: PodPlay.installTimeDeliveryExample). This file also references the main app with the line of code at the top of the dependencies curly brackets:

implementation project(":app")

By adding those two lines, you, or the Android Studio assistant, create a relationship between the app and the module.

Adding a screen to your install-time delivery module

Adding files to modules is a pretty straightforward process. It’s the same process that you use for your main module. You are going to add a screen to see how it works.

Right click the module and go to com.raywenderlich.installtimedeliveryexample ▸ New ▸ Activity ▸ Settings Activity:

Leave all the default settings like this:

Clicking this creates a dummy settings activity to test your module.

Go back to your main module and open res ▸ menu ▸ menu_search.xml. Add the following item to the menu:

<item
    android:id="@+id/install_time_delivery_button"
    android:title="@string/install_time_delivery"
    android:icon="@android:drawable/ic_menu_preferences"
    app:showAsAction="collapseActionView|ifRoom"/>

Then, open PodcastActivity.kt and look at the top of the file. The Activity already has two variables for this item, isInstallTimeModuleAvailable and settingsMenuItem.

At the bottom of onCreateOptionsMenu, right before the last return true, add:

val SETTINGS_CLASS_NAME = "com.example.installtimedeliveryexample.SettingsActivity"
val TAG_ACTIVITY = "PodcastActivity"

var settingsMenuItem = menu.findItem(R.id.install_time_delivery_button)

try {
  Class.forName(SETTINGS_CLASS_NAME)
  isInstallTimeModuleAvailable = true
  settingsMenuItem.isVisible = true
} catch (e: Exception) {
  settingsMenuItem.isVisible = false
  isInstallTimeModuleAvailable = false
  Log.d(TAG_ACTIVITY, "Couldn't start SettingsActivity, the class doesn't exist")
}

if (isInstallTimeModuleAvailable) {
  settingsMenuItem.setOnMenuItemClickListener {
    val intent = Intent().setClassName(this, SETTINGS_CLASS_NAME)
    startActivity(intent)
    true
  }
}

Build and run. You’ll see a tool button at the top. Click it, and it’ll redirect you to the settings screen.

Testing your app with and without the modules

You will want to ensure your app works with or without some of your modules in certain cases. For example, maybe you want to disable a module for your users but want to be sure it doesn’t break the app. To configure this, go to Run ▸ Edit Configurations… in the top menu:

You can check or uncheck the modules you want to install in that menu when running your app. Uncheck it now and change the variable isInstallTimeModuleAvailable at the top of the file to false.

Build and run. The main app functionality will remain unchanged, but your settings will only be available if you include it in your build configuration.

Note: Depending on the version of Java you have installed, you may encounter an error with the text: Invoke-customs are only supported starting with Android O (–min-api 26). If that’s the case, go to your module’s build.gradle and inside the android { … } part of the gradle file add:

compileOptions {
  sourceCompatibility JavaVersion.VERSION_1_8
  targetCompatibility JavaVersion.VERSION_1_8
}

On-demand delivery

Creating an on-demand module is similar to creating an install-time module. You will go through the same steps you followed for install-time, but with some tweaks.

Create your new module by clicking File ▸ New… ▸ New Module. The menu will look like this:

After clicking this, you’ll get a menu with the different modules you can create. Since you’re focusing on on-demand delivery in this part of the chapter, select “Dynamic Feature”, as shown here:

  1. Module name: Two modules can’t have the same name, so name this one onDemandDeliveryExample.
  2. Package name: This needs to be unique too, so put something like com.yourdomain.ondemanddeliveryexample.

After inputting all the data, click Next. Another window will appear, letting you configure your module’s behavior and the user-facing name.

  1. Module title: Make sure this one is also unique, although it may not be required depending on your version of Android Studio, you don’t want to get confused between your modules.

  2. Install-time inclusion: Last time, when you reached this prompt, you chose Include module at install-time. This time, select Do not include module at install-time (on-demand only).

Check out the picture below to see how the options look before clicking Finish.

Click Finish and once again wait until the module appears. Now you have an app with three modules:

That’s about all you need to create an on-demand module. But, as its name states, you need to request this module for your users to get it. You’ll learn that next.

Play feature delivery

Google provides Play Feature Delivery, an API for getting on-demand modules. This API is available for Kotlin, Java, Android NDK and Unity. For this example, you’ll focus on Kotlin.

Open your app’s build.gradle located in Gradle Scripts ▸ build.gradle(Module: PodPlay.app). Now, look at the dependencies section of the code, which is the last group with curly braces, and you will notice the following:

implementation "com.google.android.play:core:$google_play_core_version"
implementation "com.google.android.play:core-ktx:$google_play_core_ktx"

The first line is the standard library that works for Android and Kotlin. The second one adds optional functionality specific to Kotlin, like coroutines.

You are now ready to create your first dynamic feature!

Split install manager

To get the on-demand module, you need to tie it to a button, or any other part of your app, to download it. There are no limitations on where you can trigger this download.

Adding a button to trigger the on-demand download

Now, you’ll add a button in the top menu bar on the home screen, next to the search icon. Navigate to menu_search.xml, which you can find in the main app module ▸ res ▸ menu. Switch to the editor’s code view and add the following lines before the </menu> closing tag:

<item android:id="@+id/download_on_demand_module_item"
 android:title="@string/download"
 android:icon="@android:drawable/stat_sys_download"
 app:showAsAction="always"/>

Look at the preview in the editor. The menu will look like this:

In app ▸ java ▸ ui ▸ PodcastActivity, open PodcastActivity.kt. There, you have a function called onCreateOptionsMenu, which is a function you can add in any activity for storing all the code related to your menu items. At the bottom of this function, before the return true statement located above the closing bracket, add:

downloadMenuItem = menu.findItem(R.id.download_on_demand_module_item)

downloadMenuItem.setOnMenuItemClickListener {
 downloadModule()
}

In the code above, you get the XML item you created for the menu and set its onClickListener to a function call to downloadModule(), which you’ll implement next.

In the same file, add a new function with the following code:

private fun downloadModule(): Boolean {
  // 1
  val splitInstallManager = SplitInstallManagerFactory.create(applicationContext)

  //2
  val request = SplitInstallRequest
    .newBuilder()
    .addModule("onDemandDeliveryExample")
    .build()

  //3
  splitInstallManager
    .startInstall(request)
    .addOnSuccessListener { sessionId ->
      Toast.makeText(
        applicationContext,
        "Module installed successfully with sessionId $sessionId",
        Toast.LENGTH_LONG
      ).show()
    }
    .addOnFailureListener { exception ->
      Toast.makeText(
        applicationContext,
        "Module not installed with exception $exception",
        Toast.LENGTH_LONG
      ).show()
    }

   return true
}

This function is pretty long, so lets take a look at it step-by-step:

  1. You use SplitInstallManagerFactory, a method the Play Feature Delivery API provides for making operations on modules.
  2. You create a request to get the module with the name "onDemandDeliveryExample". This builder is the same for all modules. The only change is the module’s name.
  3. You execute the install and add a listener for the success and error cases. In this case, you’re adding a toast to show whether it was possible to install it or not.

You may need to import some new classes if they were not automatically included by Android Studio:

import android.widget.Toast
import com.google.android.play.core.splitinstall.SplitInstallManagerFactory
import com.google.android.play.core.splitinstall.SplitInstallRequest

Build and run. Click the download button, and you’ll see the success toast:

Note: In the latest versions of Android Studio you may be getting an issue saying Task :app:checkDebugLibraries FAILED Execution failed for task ':app:checkDebugLibraries'. [:InstallTimeDeliveryExample, :onDemandDeliveryExample] all package the same library [androidx.concurrent:concurrent-futures]. To fix this open build.gradle (:onDemandDeliveryExample) and delete all the dependencies except implementation project(":app"). After that change the dependencies bracket should look like this.

dependencies {
    implementation project(":app")
}

Conditional delivery

Now, let’s take a look at the third type of dynamic module, conditional delivery modules. These modules are only delivered when the device has specific capabilities.

Follow the same steps you did for the previous two modules, and stop at Install-Time inclusion. Select Only include module at install-time for devices with specified features. You’ll notice a small button appears with the legend + device-feature. Click it, and another dropdown will appear.

As this is a podcast app, say you only want to install this feature for devices with an audio output. Select Name, meaning you’ll determine the condition for the name of the feature you want the users to have.

Try typing a couple of letters and notice how Android Studio suggests features. Select android.hardware.audio.output, which will tell the AAB to install the feature only if the device has an audio output. Otherwise, why would the user want a podcast app?

Look at the image below to see how the dialog looks before clicking finish:

Note: You can add as many conditions as you want for your module. Maybe you have several pre-requisites for a specific module. You can add all of them.

Just as in the previous examples, after a couple of seconds, the new module will appear. Now your app has four modules, and the project looks like this:

Conditional delivery by country

You can also download individual modules depending on the user’s country. This is another option for conditional delivery that isn’t available from the New Module assistant.

For example, you may want to support specific payment methods for different countries. You can create a module for each country’s methods to provide users with only the necessary options.

Go into the AndroidManifest.xml of the sample module you created. You’ll notice Android already provides a comment on how to make country-based conditionals, like in the image below.

Replace that comment with:

<dist:user-countries dist:exclude="true">
  <dist:country dist:code="US" />
</dist:user-countries>

Now your file will look like this:

Look at the code of this file. You’ll see that this module now has two pre-conditions, inside the tag <dist:conditions>:

  1. Located in the US.
  2. Having a device with audio output.

That’s it for conditional delivery. If the device fulfills the requirements, it’ll install these modules at download time.

Checking whether the conditional module was downloaded

Just as you did in the previous examples, you will add a button in the navigation bar that will show a toast telling you whether the module was installed or not.

Navigate to menu_search.xml, which you can find in the main app module ▸ res ▸ menu. Switch to the editor’s code view and add the following lines before the closing tag:

<item android:id="@+id/download_country_restricted_module"
  android:title="@string/download"
  android:icon="@android:drawable/btn_star"
  app:showAsAction="always"/>

The code above will show a star icon, which you will be able to see in the preview of the xml editor.

In app ▸ java ▸ ui ▸ PodcastActivity, open PodcastActivity.kt.

At the top of this class add:

 private lateinit var downloadConditionalModule: MenuItem

Now, look for a function called onCreateOptionsMenu, where you added the code for the on-demand delivery module. At the bottom of this function, before the return true statement located above the closing bracket, add:

downloadConditionalModule = menu.findItem(R.id.download_country_restricted_module)

downloadConditionalModule.setOnMenuItemClickListener {
 downloadConditionalModule()
}

In the same file add a function at the bottom for the execution of the code above.

private fun downloadConditionalModule(): Boolean {
  // 1
  val splitInstallManager = SplitInstallManagerFactory.create(applicationContext)

  //2
  val request = SplitInstallRequest
    .newBuilder()
    .addModule("conditionalDeliveryExample")
    .build()

  //3
  splitInstallManager
    .startInstall(request)
    .addOnSuccessListener { sessionId ->
      Toast.makeText(
        applicationContext,
        "Conditional Module installed successfully with sessionId $sessionId",
        Toast.LENGTH_LONG
      ).show()
    }
    .addOnFailureListener { exception ->
      Toast.makeText(
        applicationContext,
        "Conditional Module not installed with exception $exception, you don't match the conditions",
        Toast.LENGTH_LONG
      ).show()
    }

   return true
}

This code is very similar to the previous examples, but in case you want to look at what it means:

  1. You use SplitInstallManagerFactory, a method the Play Feature Delivery API provides for making operations on modules.
  2. You create a request to get the module with the name "conditionalDeliveryExample". This builder is the same for all modules. The only change is the module’s name.
  3. You execute the install and add a listener for the success and error cases. In this case, you’re adding a toast to show whether it was possible to install it or not.

Note: If you are using an Android emulator this may be tricky, as the set country may be different than the one you are located on, so preferably, test it in an actual device. If you keep having problems you may need to deploy the app to a beta lane in the Google Play Store.

Instant delivery

Instant delivery is the last option for dynamic features. It’s interesting and has gained a lot of traction, especially in mobile games. With instant delivery, you can let the user test a feature from your app without downloading it. Instant delivery is an excellent tool for showing users the best part of your app and potentially engaging new users.

However, making an instant delivery module requires pre-conditions that may be hard to achieve.

  1. The user needs access to the feature you want to instant-deliver plus your app base module.
  2. Both of these modules together shouldn’t be larger than 10Mb.
  3. Your base module shouldn’t use any background services.

Yes, it sounds a little tricky, and it can be. If you want to do this, you have to do a lot of planning and get specific.

The first step to creating an instant module is analyzing your APK, as you did in the previous chapter. Click Build ▸ Build Bundle(s) / APK(s) ▸ Build APK(s). Then, in the same menu, go to Build ▸ Analyze APK…. Look at the images below for reference:

After the analysis finishes, you’ll see the results in the main window. PodPlay will be around 5-6MB. For a detailed explanation of how to interpret this data, go to the previous chapter.

With these results, you know your main module is below the 10Mb mark, and you can create an instant module. Yay!

Creating an instant module

As you did in your previous examples, go to File ▸ New ▸ New Module…. This time, instead of selecting Dynamic Feature Module, choose Instant Dynamic Feature Module. As the name explains, this option is for instant apps.

You’ll notice the configuration screen is almost the same as the one in Dynamic Feature Module except all the options are on one screen instead of two.

By now, you probably know what to put in each option. But, just in case, look at the image below to see what you should add:

Click Finish and, as in the previous examples, wait for the module to appear. Now, you have five modules in your app.

As you learned before, the assistant automatically adds specific properties to your created module’s AndroidManifest.xml. In the case of instant apps, you’ll notice this line:

dist:instant="true"

With the above you have now enabled a module that will appear when the app is downloaded as an “instant” experience. To test this on Android Studio, follow this steps:

  1. Delete previous versions of your app from the device or emulator you are currently using to test.
  2. Go to Run ▸ Edit Configurations

  1. Select the checkmark next to Deploy as Instant App.

  1. Now you can run that module as if you were running your full app.

Once you are running an Instant App test, ensure you are seeing only that module and that you can’t accidentally access other modules or the app will crash.

You’ve learned everything you need to build dynamic modules with your AAB. Now you can choose which one to add to your app or make a mix of them. Adding this will provide a great user experience!

Feature flagging

Besides AABs’ capabilities, Feature flagging is a software development term that’s been around for a long time. Feature flagging refers to enabling or disabling features depending on a specific condition. Ideally, a server would control the flags to enable or disable them depending on the app’s needs.

Here’s an example. Imagine that in PodPlay, you created a module for taking notes about the podcasts.

Once again, go to menu_search.xml, which you can find in the main app module ▸ res ▸ menu. Switch to the editor’s code view and add the following lines before the </menu> closing tag:

<item android:id="@+id/write_notes_item"
 android:title="@string/notes"
 android:icon="@android:drawable/ic_menu_edit"
 app:showAsAction="always"/>

Look at the preview in the editor. The menu will look like this:

In app ▸ java ▸ ui ▸ PodcastActivity, open PodcastActivity.kt. There, you have a function called onCreateOptionsMenu, which is a function you can add in any activity for storing all the code related to your menu items. At the bottom of this function, before the return true statement located above the closing bracket, add:

notesMenuItem = menu.findItem(R.id.write_notes_item)

visibilityOfNotesFeature()

You want to test the feature. So, put a flag on it with the following code:

private fun visibilityOfNotesFeature(): Boolean {
 notesMenuItem.isVisible = areNotesEnabled
 return true
}

Now, you need the areNotesEnabled feature flag. At the top of your class, where you initialize all variables, create a new variable with:

 private var areNotesEnabled = true

Run the app twice: Once with the Boolean areNotesEnabled set to true and the other with it set to false. You’ll notice the pencil icon only appears when the variable is true, like in the image below:

In a real case scenario a backend service would pass a parameter in a service call that the app would use to determine the state of the feature flag. Depending on the returned state, your app can show or hide this option.

Why would you want to implement a feature flag?

There are several reasons for implementing a feature flag. Some examples include:

  1. While developing, you want to control which modules to view to make your testing more efficient.
  2. Marketing teams can make user tests to see user engagement with or without a feature.
  3. You can release unfinished features when you have a Continuous Delivery pipeline and add all your code without enabling access to those unfinished parts. You’ll learn about Continuous Delivery pipelines later in the book.

Key points

  • Android App Bundles provide dynamic delivery options to make your app lighter and better meet your users’ needs.
  • You can create install-time delivery modules that are added to the app immediately when the user downloads your app.
  • Users can request on-demand delivery modules, depending on their needs.
  • Another option is conditional delivery modules, which will download depending on various factors like hardware requirements, version of the device or country.
  • Instant apps are an excellent option for engaging your users, but you have to ensure your base module and instant app module are not larger than 10MB.
  • Feature flagging is a technique used in Software Development for controlling the visibility of features and the functionality of your app. Ideally, the back end of your app would change this data.

Where to go from here?

There’s a lot to discover about Dynamic Features. For more on this topic check out Android App Bundles: Getting Started and Instant Apps: Getting Started, which will help you practice the skills you just learned.

Looking forward to seeing the amazing modules you add to your app.

Have a technical question? Want to report a bug? You can ask questions and report bugs to the book authors in our official book forum here.
© 2026 Kodeco Inc.