Part 1: Dynamic Delivery in multi-module projects at Bumble

Mar 17, 2020 · 8 min read

Dynamic Delivery is a technology which allows you to install and delete parts of an application while the application is working, in order to reduce the space it occupied. If there are functions not being used, what is the point of a user having them on their device?

In part 1 of this article, I explore Dynamic Delivery and its API in more detail, specifically how to download and remove modules. In part 2, based on an example, I will unpack how I used Dynamic Delivery in our application and reduced the size of the application, saving half a megabyte of space.

So, let’s get started!

Modules for Dynamic Delivery

Functions which a user does not need, and which can be deleted, can be said to include the following:

  1. functions relating to А/B tests and user groups. Some functions may only be accessible in certain regions and, without Dynamic Delivery, on all other users’ devices, they are just dead weight.

These kinds of functions can easily be moved to separate downloadable modules to reduce the space the application occupies. If this module is not installed at the same time as the application, then the size of the application, as displayed in Google Play, will be reduced accordingly. Smaller application size translates into a greater number of installations. It is also important to remove unused modules in order for your application to take up less space. If a device is running out of space, Google Play gives the user the option to delete some applications, and it also sorts applications according to the space they occupy. And you don’t want to be at the top of that list.

Modules with the functions listed above may contain code and any type of resources. Following installation, classes get loaded into a ClassLoader and may be used. You will be able to access the resources from the installed module. However, there is a ‘but’…

Dynamic Feature Module

You can only work with the downloaded code using reflection. This ensures that the dynamically downloadable modules are secure to use. If the downloadable module was connected using the compileOnly dependency, then, when attempting to use this module’s classes, if this module has not been installed,

ClassNotFoundException would be returned. In such case, reflection allows you to:

  1. handle situations more securely, when the required classes are not to be found in runtime.

This is what it looks like from the point of view of module structure in Gradle:

Source of picture: Patterns for accessing code from Dynamic Feature Modules, recommended reading

The modules in the first row (:about and others) are Dynamic Feature Modules. They depend on the application’s basic module and are able to use its code and resources easily. The application module is in the second row and its dependencies are in the third.

It might seem that the requirement to use reflection nullifies all the advantages of the technology, but I will show you how, in the case of our multi-module approach, all the reflection can be reduced to just two or three calls.


To start with, let’s see how to install modules. For this, we use SplitInstallManager, which is part of the library. You may already be familiar with this from In-app Updates and MissingSplitsManager.

Here is how to work with modules:

  1. using SplitInstallManager.installedModules verify that we do not already have the module installed.

It’s all quite simple and self-evident, with the exception of one not very convenient API, which I will show you using the example of code from

Verifying the module has been installed

Request installation

splitInstallManager.startInstall will return Task<Int>, but not one from the package, to which in your case the Kotlin Extensions have most likely already been written, but rather its own one. Their API coincides entirely, but the package names are different. An installation session identifier is returned in addOnSuccessListener callback. What’s more, the same one is returned if you request installation several times over, so don’t be afraid to do so. There is only one limitation: if you specify downloading several modules at once via SplitInstallRequest.addModule(…).addModule(…), then when you attempt to request installation of just one of them on a second or subsequent occasion, it will return the error INCOMPATIBLE_WITH_EXISTING_SESSION. If the error occurred before the session was accepted, or during installation, an error will be returned in addOnFailureListener.

Progress of installation

Installation state updates will be available in SplitInstallStateUpdatedListener. Updates of all sessions are available here, and we need to filter them ourselves based on the session identifier. In SplitInstallSessionState the following are available to us:

  1. current installation state (download, unpacking, installation etc.).

Confirmation from user

Whenever the size of the module exceeds 10 MB you need to ask the user for confirmation of download. In SplitInstallSessionState a special state, REQUIRES_USER_CONFIRMATION, is returned. This will be the state of installation until you call splitInstallManager.startConfirmationDialogForResult(Activity, SplitInstallSessionState, Int). This call will run Google Play via startActivityForResult with an installation confirmation dialogue. If the user clicks on the ‘download’ button, then installation will continue, and you don’t need to do anything. If they click on the ‘cancel’ button, then installation will end with a CANCELED state.


To support the load of classes and resources in an application, you need to use SplitCompat.

The application version will extract classes.dex from the downloaded APK files and will load them into ClassLoader, and will also call context.getAssets().addAssetPath(String) with the downloaded module’s APK file. The Activity version will just add the path to the AssetManager. It might seem unnecessary to call installActivity if you are using the application’s Context, but not doing so can cause problems with the configuration of Activity, which, in that case, will be ignored.

Deferred installation

You can request Google Play Services to install a module at some point in the future. The official documentation describes the time “at some point in the future” as “best-effort when the app is in the background”. In practice, the module will be downloaded when your application is not running and when Google Play is installing updates for your application or others.

Requesting installation in the background is very simple:

At the same time, there is no way for you to track it, for the simple reason that it won’t run as long as your application is running.

Despite this limitation, this approach may well still be useful. For example, for functions relating to an A/B test. If you have positioned the entry point to an application’s new screen somewhere that is visible, then the user might click on it — at the very least out of curiosity. Then why not request installation of such modules in the background, so the user won’t have to wait at a later point in time?

Pulling it all together

For a start, let’s write a function to verify whether the module, moduleName, has been installed and whether the required class, className, can be used via reflection.

In the case of non-release builds the modules installed will be empty. So, we add verification as to whether or not there is a class — which we intend to use via reflection. We use exactly this reload of Class.forName in order not to initialise static fields of the class and perform work that could be left until later.

During the installation process we have to handle various module installation states. For this, we use a simple sealed class.

Unfortunately, we cannot entirely go away from using SplitInstallSessionState in RequiresConfirmation, since it is essential for calling splitInstallManager.startConfirmationDialogForResult. However, we only seem to need a session identifier. I hope this will change in the future.

We also need a function that will download a given module and track progress. At Bumble we use a reactive approach, so we will return Observable<DynamicDeliveryProgress>. Once the module is installed, we will complete observable.

From the UI side don’t forget to handle the DynamicDeliveryProgress.RequiresConfirmation state. Once the installation is completed you will need to call SplitCompat.installActivity(this) again, in order to download resources from downloaded APK files.

In this implementation we don’t deal with the INCOMPATIBLE_WITH_EXISTING_SESSION state at all, since, in our case, all the modules are installed one-by-one. However, this error is quite easy to deal with. When it occurs in retryWhen you can create an Observable which:

  1. again subscribes to state updates via splitInstallManager.registerListener.


Dynamic Delivery from Google lets you download and delete modules while the application is working. This is an excellent way of saving space on a device: as a rule, there are modules in an application which are rarely used, but if necessary they can be loaded while the application is working.

Despite the not-so-convenient API it is entirely possible to hide all the module download operations behind a single interface. It is easy to execute a request for downloading Dynamic Delivery modules, but you have to be careful when processing it’s dozens of different states.

There are two other points which merit pointing out:

  1. once the module is installed, don’t forget to notify the current Activity and so ensure that it has access to the resources downloaded.

In part 2 of the article, I will tell you how I used Dynamic Delivery for one of Bumble’s projects.

Bumble Tech

This is the Bumble tech team blog focused on technology and…

Medium is an open platform where 170 million readers come to find insightful and dynamic thinking. Here, expert and undiscovered voices alike dive into the heart of any topic and bring new ideas to the surface. Learn more

Follow the writers, publications, and topics that matter to you, and you’ll see them on your homepage and in your inbox. Explore

If you have a story to tell, knowledge to share, or a perspective to offer — welcome home. It’s easy and free to post your thinking on any topic. Write on Medium

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store