Chapters

Hide chapters

Real-World Android by Tutorials

First Edition · Android 10 · Kotlin 1.4 · AS 4

Section I: Developing Real World Apps

Section 1: 7 chapters
Show chapters Hide chapters

9. Dynamic Features Theory
Written by Ricardo Costeira

In real life, you’ll probably have many more modules in your app than PetSave has at this point. You might also have some kind of analytics logic in the app, which gives you insight into how users interact with it.

Suppose you have a share animal feature module. This feature allows the user to share an animal’s information on their social networks. Through analytics, you know that this feature is seldom used. However, even users that don’t want to use the feature still install it, taking up precious disk space.

Android app size has a direct relationship to the number of app installs and user retention, with larger apps tending to have fewer installs and lower user retention.

Say that the share animal feature module takes up half the space of the whole app. Wouldn’t it be frustrating to see your app being frequently uninstalled due to a large feature module that almost no one uses?

The Android team is aware of this, so they came up with some mechanisms to mitigate the problem. One of these mechanisms is the app bundle publishing format. Using this publishing format can already help you reduce your app size.

For the most part, however, this chapter focuses on another mechanism: Play Feature Delivery. This mechanism takes advantage of advanced app bundle features to allow you to develop dynamic features.

This chapter is optional if you already know what app bundles and dynamic features are. On the other hand, if you don’t understand those concepts thoroughly yet, what you read here will help.

The chapter focuses on the theoretical side of dynamic features. You’ll learn about:

  • The app bundle publishing format.
  • What dynamic features are.
  • The delivery options for dynamic features.
  • Two of the most common challenges with dynamic features: dependency injection and navigation.

Android app bundle

Before diving into dynamic features, you need to know about app bundles. An app bundle is Google’s clever app delivery format, which splits the APK into different pieces. It then delivers only the pieces the user’s device requires.

When you upload an APK to Google Play, all users receive that universal APK when they download your app. When you upload an app bundle, however, Google Play uses it to create a few different APKs, called split APKs. These split APKs are available from Android API 21 onwards. There are three different types:

  • Base APK: Google Play generates this APK from the app module of the app. It contains everything you need to configure and launch the app as well as shared code, in most cases. It’s the first APK that the user downloads and installs.

  • Configuration APKs: APKs related to different screen densities, languages, CPU architectures or native libraries. When the user downloads the app, Google Play installs only the configuration APKs related to the user’s device.

  • Dynamic feature APKs: APKs with code and resources for each dynamic feature.

Even if you don’t care about dynamic features, it’s a good idea to use app bundles. If your app is properly modularized, it’ll reduce the final app size. Apart from that, Google is requiring that new apps submitted to Google Play will use app bundles starting from the second half of 2021. Moreover, apps larger than 150 MB will have to use either Play Feature Delivery or Play Asset Delivery.

Play Feature Delivery delivers dynamic features to the user via app bundle features and APIs. You’ll learn more about these later. As for Play Asset Delivery, the logic is the same as Play Feature Delivery, but it applies to game assets.

Dynamic delivery

Google Play installs the split APKs on the user’s device and makes them appear as a single app. This is called an optimized APK. This optimized APK is built through a process called dynamic delivery. This optimizes the APK because dynamic delivery generates it using only the components that matter for the user’s specific device.

For instance, suppose you have a Portuguese-speaking user, like yours truly. My device has a resolution of 560 dpi and runs on an ARM 64 processor. When I download an APK that uses dynamic delivery, I’ll get:

  1. The base split APK.
  2. The split APKs for ARM 64, Portuguese string resources and xxxhdpi resources.
  3. Any split APKs for install-time dynamic features. You’ll learn more about dynamic delivery types later.

With dynamic delivery, you don’t need to manage and optimize multiple APKs for different devices anymore. The process manages them for you!

Another advantage is that creating an optimized APK from split APKs makes it possible for you to decide which APKs to deliver. More specifically, it lets you pick which dynamic feature APKs to deliver.

What are dynamic features?

When you have a multi-module app, each feature usually has its own module. If you do it right, each feature module will — for the most part — be independent. You still need an application module to use the feature and it might depend on a few core modules, but everything that defines the feature will be in its module. This module isolation is essential to creating dynamic features.

A dynamic feature module is similar to a normal feature module. Apart from the details that allow the Android framework to handle it as a dynamic feature, there are two main differences from normal modules: Play Core and module dependencies. Next, you’ll learn more about each one.

Play Core

The first main difference is that you can specify how and when the user can access a dynamic feature. You can even define when the feature is installed, or uninstalled. This behavior is possible due to the Play Core Library.

The Play Core Library is what your app uses to interface with the Google Play Store. Although only its dynamic feature capabilities are important here, it allows you to do a few interesting things:

  1. Download resources
  2. Manage feature module delivery
  3. Manage asset pack delivery
  4. In-app updates
  5. In-app review

Using the Play Core API, you can implement your own logic to decide how to handle dynamic features. Google Play Store handles the rest.

Play Core provides four different feature delivery options:

  • On demand delivery: The app asks to download features under specific conditions, which is useful for features that most users don’t need. You can have the app download those features only when the user tries to use them.
  • Install-time delivery: This option installs dynamic modules with the app, along with all regular modules. You can then request to uninstall them later, which is useful to get rid of one-use features, like onboarding.
  • Conditional delivery: This installs dynamic features according to certain device conditions. For instance, the app could install specific features for specific API levels. Or, the app could download camera-related features only when the device has a camera. If the conditions hold true, the feature downloads at install time.
  • Instant delivery: You’ve probably heard of instant apps by now. Instant delivery lets users try specific features without having to install the whole app. This one’s more complex than the others because there are specific — and very strict — size requirements for the base module.

To implement any of these options, you need to properly set up the dependencies between the base and dynamic feature modules.

Module dependencies

The second main difference between regular feature modules and dynamic feature modules is the way the dependencies between base and feature modules work. In a regular multi-module app, the app module depends on feature modules:

Figure 9.1 — Typical dependency graph between base module and regular feature (library) modules.
Figure 9.1 — Typical dependency graph between base module and regular feature (library) modules.

Things are a little different when using feature modules. Because dynamic feature modules can be installed at any point, they might not come with the app when you install it. As a consequence, the base module can’t depend on dynamic features. After all, it can’t depend on something that might not exist!

On the other hand, the base module contains the app configuration, along with access to all shared code from core modules. For these reasons, dynamic feature modules depend on the app module. This translates into an inversion of dependencies:

Figure 9.2 — Dependency graph between base module and dynamic feature modules.
Figure 9.2 — Dependency graph between base module and dynamic feature modules.

Note that this doesn’t mean the app module is completely unaware of dynamic feature modules. It can’t access code from dynamic feature modules — at compile time, at least. Still, it’s somewhat aware of their existence through Play Core.

This inversion of dependencies introduces new challenges in modularization. The most notable ones are with dependency injection and navigation between features.

Injecting dynamic dependencies

PetSave uses Hilt for dependency injection. Hilt requires the entire dependency graph to be built at compile time. Hilt builds the dependency graph starting at the Application annotated with @HiltAndroidApp. This class is in the base module, which works out of the box for a monolithic app. For a multi-module app, it’ll work as long as the base module is aware of all the dependencies. You’ve already seen how to make this work in Chapter 8, “Multi-Module Apps”.

With dynamic features, the base module can’t access all the feature modules. This means that the dependency graph is forcefully split into different pieces. In other words, Hilt can’t figure out the dynamic modules’ dependencies.

The Hilt team expects to fix this at some point, but only time will tell. For now, you can use Dagger to work around this. You’ll see how in Chapter 10, “Building a Dynamic Feature”.

Navigation with dynamic features

You’ve already learned that navigating between features in multi-module apps is a challenge. In the previous chapter, you solved the problem using the Navigation component with deep links.

The Navigation component has some support for dynamic features, but it has some limitations as well. It lets you dynamically include navigation graphs, for instance. However, it does not support deep links for dynamically included graphs. Gotta love these Android framework caveats. :]

You have a few options here:

  1. Using reflection. Ew.
  2. Creating a new com.android.library module that has interfaces for dynamic features. This module would depend on both feature and base modules. At runtime, you’d load the dynamic features with ServiceLoader. However, this option is no longer viable because R8 doesn’t support using ServiceLoader for dynamic features anymore. Using ServiceLoader without R8 optimization is a bad idea for performance reasons.
  3. Using a Navigation component feature called DynamicNavHostFragment. It replaces NavHostFragment and lets the navigation controller navigate to dynamic features.

Reflection works, but it’s neither the safest option nor the most performant. The second option would also affect performance since you can’t use R8. So, in the next chapter, you’ll go with option number three.

In fact, this is all the theory you need to start implementing dynamic features. It’s time to get your hands dirty!

Key points

  • App bundle is a publishing format that optimizes and tailors APKs for users’ devices.
  • Play Feature Delivery uses advanced app bundle features that allow you to optimize app installations to the next level.
  • The Play Core Library provides the mechanisms for you to decide how and when you want to deliver dynamic features.
  • Navigation and dependency injection become challenging with dynamic features.
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.