Image for post
Image for post

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

Yury
Yury
Mar 19 · 9 min read

In part 1 of my series of articles, I have already explained to you what Dynamic Delivery is and what API it has. In this article, I describe in detail exactly how I used Dynamic Delivery in our application Bumble and why, in particular, integration was so easy. As a result, I was able to reduce the size of the application by half a megabyte for 99% of our users, turning a function which was available only in a given geolocation into a downloadable module.

I hope this will be as useful as it was for me :)

Bumble Brew

Recently we have been experimenting with offline interaction with users and have added a new screen featuring a QR code which when scanned at the door would allow you entry into a restaurant, cafe or shop, for example. This is what it looks like:

Image for post
Image for post

There is no particular logic to it, but it has a very attractive background. In fact, given its maximum size it is 547-KB-attractive. The specific nature of this screen means that it’s only going to be used by a very small number of users, for example by city dwellers heading for an offline meeting venue. But all users still have half a megabyte of space taken up on their devices. Basically, this makes it an ideal candidate for Dynamic Delivery.

Preparing the Brew module

We use RIBs architecture when creating screens. Articles on this architecture will appear in the future, but for now, to make it easier to understand, we are going to consider as a . This is because the underlying idea is the same: there is a self-contained screen element with or without UI, that can either be integrated into other elements or can integrate them itself.

In a multi-module app for whatever screen module it is, whether or , the following is true: it has a public API which describes the interaction with this screen from outside; and an internal API which is needed for the functioning of this screen. Usually we segregate public and private API using access modifiers. However, you can go further and separate public and private API into two different modules: and .

In the first module we describe how to work with this screen, and the dependencies it has.

The whole public API fits into 4 interfaces, which the application module will work with. declares dependencies needed by this screen. acts as a callback for obtaining results from this screen; provides information about what is needed and how to display it on-screen (the screen does not perform a network query itself, but receives the results of an external network query); is for analytics tracking. allows you to change the appearance of the screen, for example, replacing the logo and background with branded alternatives. This approach makes it much easier to adapt existing screens for other applications. is the result of the screen. In our case, you can only close the screen. However, in future there may be a new event, for example, for opening another screen in the application.

In the second module we implement all the logic and UI for this screen.

Image for post
Image for post

Practically all the classes of this module are , apart from . is a factory that will create an instance of , which we can use in future.

Creating this sort of factory is extremely important for Dynamic Delivery, as it is very easy to use via reflection. All dependencies are expressed by one interface, which will be available without reflection, which means that it can be implemented inside the application module. The creation process itself also requires calling with only one previously known parameter.

DynamicDeliveryContainer

Since all implement a single interface, so this means that a generalised container can be created for all dynamically downloadable screens.

The implementation of does the following:

  1. it asks , the implementation covered in part 1, whether the module is installed or not.
  2. if the module is installed, then it calls and attaches the created .
  3. if a module is not installed, then it requests installation of the module and displays some attractive UI.
  4. once the module is installed it replaces the attractive UI with a downloaded .

Identical logic can be implemented easily with the help of fragments since fragments also support child fragments being deployed inside them.

Configuration of Dynamic Feature Module

Dynamic Feature Module is an ordinary Gradle module. Based on our example let’s look at the configuration:

Android Gradle Plugin contains a new plugin for these kinds of modules: . At the same time, using this plugin imposes no limitations in practical terms; it’s fine to reuse configuration scripts for your modules ().

Documentation states that and should not be set since these values are automatically received from the application module. Yes, that’s right; only these values can be cached. I have encountered a situation where the project stopped building after these values changed in the application module. I therefore decided to manually install a version, reusing the application’s file.

The dependencies which you specify will be correctly handled by the plugin. Only those dependencies which are not in the main module will be included in this module. In this case, it is just .

At the CI testing stage, I discovered that not only doesn’t run but cannot even build. For some reason, when attempting to build a module for testing instead of behaving like a Dynamic Feature Module, it behaves like an ordinary one. For this reason, lots of errors occur at the stage when manifests and resources merge. All the tests are either in the application module or in the screen module so this means that there’s no point even attempting to run them. For this reason, I switched off completely all the tasks associated with .

This is what the manifest for this type of module looks like:

This is where we configure module behaviour. In this case, the module is not Instant App; it will be installed and has an entirely localised name: . It is important to have an entirely localised name since this name will be shown in the Google Play dialogue.

Image for post
Image for post
In this case, the localised name of the module is Brew, and it will be used in its uninflected form in all languages; the size of the module was increased for the purposes of the test.

You can also configure whether this module needs to be installed, when the application itself is being installed. This may be useful when you are making a Dynamic Feature Module from the registration screens, for deleting them after completing registration. is responsible for whether or not you need to include this module in APK for devices on Android 4.4 and lower, but we no longer support these.

Android Gradle Plugin will set the field in the manifest itself, to which it writes the name of the Gradle module from the . It is precisely this name which needs to be used for downloading the module. You cannot change this name without changing the name of the module.

You can read up in more detail about all these possible options in the documentation.

Here is how the final structure of Dynamic Feature Module turned out:

Image for post
Image for post

is responsible for providing the image identifier to . We will create this via reflection and use it to create an instance of . Since we will be deferring the creation of the until after the module installs, we won’t have any problems.

In Dynamic Feature Module be careful with resources! Those inside the module need to be accessed via this module’s class. Those in the application’s module, need to be accessed via the application’s class (as I do for ). This is very important; otherwise, you will get . You need to be extremely careful, since, due to the dependency on the application’s module, will generate an class along with identifiers for all the resources from the application’s module.

Configuration of application

First of all, we configure the application’s module.

A new property has appeared in : , in which it is essential to list all the paths to the Dynamic Feature Modules.

We also depend on , which performs the module download and displays it on the screen, and , where only interfaces for working with the screen are declared.

We will create a class where we will store all the constants for working with the Dynamic Feature Module. These constants include: the class name for the factory creating , the class name for the customisation factory and the name of the module as in Gradle.

Since these classes are used via reflection, we add them to proguard.

All that’s left is to do is to create an instance of , send the necessary parameters to it and connect it to .

The code given above can be made lighter by moving the creation of dependencies to Dagger. However, describing Dagger components and modules here would make the code more complicated, so, in the interests of simplicity, I create all the dependencies in place.

Bumble Brew is not available to all users, only to those who live in the city where a given event is being held. Also, users are included in a special user group. The application receives the list of groups which a user is part of when it first connects to the server. And that means that at this moment we can say whether or not the button for opening the Brew screen will be displayed. And at this time we can request deferred installation of the module using .

How I broke tests

In our team we use fully-fledged Е2Е tests. The testing toolbox (in my case Appium) installs the application and simulates the user touching the screen. At the same time, the application itself uses an up-to-date version of the back-end. To install the application, we use the following function:

For tests we use , from which we create APKS and install it on the device. Installation of takes place using the command. And if you also use for your Е2Е tests, don’t forget to add the parameter, in order to add all the Dynamic Features straightaway. We didn’t have this parameter so all the tests for Brew started to fail.

As I mentioned earlier, unless a given application is installed from Google Play then it is unable to install modules. Either they will all be installed straightaway — or none will be at all. For this reason, the approach with does not cover scenarios for installing this module. At the present time we use Internal App Sharing for testing Dynamic Feature.

However, Google recently released a new version of , in which FakeSplitInstallManagerFactory appeared. It is still not entirely equivalent to the ordinary , but it can nevertheless increase the quality of automated testing. It has 2 parameters for setting its behaviour:

  1. — after installing this, any installation will terminate with an error.
  2. for setting the folder on the device from which you need to install requested modules. The modules themselves are copied into this folder in the form of APK.

You can obtain the necessary APK for installation using the following command:

As a result, the APK files obtained need to be sent to the device via and the folder needs to be identified in .

Even though this approach is closer to what really happens, to use it you need a separate application build, using . You also need to introduce changes to the testing toolbox. So, it is probably worth doing it when there are more dynamic models.

To verify that reflection is correct, use ordinary unit and integration tests. In my case these are Е2Е and the integration test, which simply navigates from the main screen to the Brew screen.

Conclusions

The Google technology, Dynamic Delivery, allows you to download and delete modules while the application is actually working.

If you have a multi-module project, then it’s possible that all the modules are clearly segregated into public and private API. Creating separate modules for each of them makes it easier to use Dynamic Delivery in the project. Given this structure, you only need to use reflection for creating instances of public API classes.

When all the application screens have a similar architecture and have clearly designated dependencies, you can create a universal container for downloading and displaying Dynamic Feature Modules. Using this container, you can very quickly convert those application screens which users rarely use to Dynamic Feature. And, thus, you can reduce the size of the application even further.

Bumble Tech

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

Yury

Written by

Yury

Android Developer @BadooTech

Bumble Tech

We’re the tech team behind social networking apps Bumble and Badoo. Our products help millions of people build meaningful connections around the world.

Yury

Written by

Yury

Android Developer @BadooTech

Bumble Tech

We’re the tech team behind social networking apps Bumble and Badoo. Our products help millions of people build meaningful connections around the world.

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