Modern Way to Reduce Your Android Apps Size With Dynamic Feature

Nathaniel Khuana
Tokopedia Engineering
9 min readJan 31, 2020

Authors: Nathaniel Khuana and Hendry Setiadi

It has been more than one year since Google introduced Dynamic Feature. Many blogs and Google IO sessions have talked about this technology. We may be aware of Dynamic Feature, however many of us only know the surface of it. What if we implement Dynamic Feature in a big and multi-feature like Tokopedia app?

In this article, we want to share the benefits and also the challenges when we implement Dynamic Feature that many of us might not know yet.

Quick Introduction — What is Dynamic Feature

In Google IO 2018, we can see the statistics that show that average APK size continues to increase over time. Because of this, users often avoid download and install applications, particularly in emerging markets where many devices still connect to 2G and 3G networks. One of the solutions is to reduce app size using Dynamic Feature.

Average APK size

Dynamic Feature comes as part of Dynamic Delivery, a new way to distribute applications with a new format called Android App Bundle (AAB) to replace the old APK. With the AAB, Google Play can send the optimized installation based on device configuration and also features that only required by users. (e.g., only download the XHDPI resource instead of all resources).

One of the best parts of Dynamic Feature is users able to download a specific feature only when the module is required. For example, users will download the ‘Hotel’ module only when they open ‘Hotel’ in the application. It means the ‘Hotel’ feature will not be downloaded on the first install, which means it will reduce initial download size and installation time.

Furthermore, Dynamic Feature is configurable, which means we can set conditional delivery options, for example: by country, device feature, and API level.

Dynamic Features in Tokopedia

In Tokopedia, we have implemented the Dynamic Feature module for the seller side. The app will download Seller Dynamic Feature when the user attempts to access any of the seller features, e.g., Manage Product, Manage Order, and Manage Shop.

Below is the illustration of how users download the seller module in Tokopedia.

Dynamic Loading for Seller Feature

If the user is a buyer and s/he never opens any seller features, then the Seller Dynamic Feature will never be installed. It will give the user a pleasant experience because it will save more phone space. Using this method, we can save around 3MB, which is almost 10% of the initial download size when users download from the Play Store.

Dynamic Features Challenges Illustrated In Sample App

Implementing Seller Dynamic Feature is not as easy as we thought before. It is because the Tokopedia app is quite big and complex. Therefore, to give the illustration, we have created the sample project found on this link.

Generally, the sample project has two local libraries, three Dynamic Feature modules, and one application. Each Dynamic Feature module has independent features and specific library implementation. We can see the project structure below.

Sample Project Structure

When we discuss the obstacle that happened during the implementation, we can refer to this sample project.

Challenge #1: Modularization

One of the first challenges to create Dynamic Feature Module in the Tokopedia app is modularizing the module. What does it mean by modularization?

Suppose we have an application that has two features: feature A, feature B. The application build.gradle is like below:

dependencies {
implementation project(':featureA')
implementation project(':featureB')
}

And someday, we want to remove `featureB` from application:

dependencies {
implementation project(':featureA')
}

If the project compiles successfully, then it is modular.

So, in our case, the definition of modularity is how the module can be separated independently to other modules, and the application must not know its features strictly.

In our sample project, we create three features that independent between each module and the application, but they implement some shared libraries. For example, the “Random Image” DF can still depend on ‘Lib2’.

This part of modularization may seem easy at some point, but then it will raise a question: How can the application navigate to the other module without knowing the module itself? To answer it: we can use the string class name to achieve it.

startActivity(intent.setClassName(activity, "com.example.pkg1.Act1"))

Challenge #2: Nested Libraries Dependencies

To illustrate this, we can refer to the previous diagram that shows that “Random Image” Dynamic Feature depends on ‘Lib2’, but the “Video Player” Dynamic Feature also depends on ‘Lib2'. So, there is one library module that is used in 2 or more Dynamic Feature Modules.

Previously we expected the Play Store could handle this structure. For example, if a user wants to install “Random Image”, it will download the module plus the libraries: ‘Lib2’ and ‘Lib1’. Then, after that, if s/he wants to install “Video Player”, it will download “Video Player”, without re-download Lib2.

However, what happens is the project cannot be compiled because the library that is included in two or more Dynamic Features cannot be included as part of the download. This issue is mentioned in this issuetracker.

So, the solution is we have to include the libraries in dependencies of the base application, even though the app might not use that library directly.

dependencies {

implementation project(":lib2")

}

Fortunately, the Google team now provides the solution named “FeatureOnFeature” that allows us to make the Dynamic Feature to depend on another Dynamic Feature. So, if we have nested dependencies in our application, it would be better to leverage this feature.

Challenge #3: Resource Used in Cross Modules

Some issues also occur when we use resources from other libraries.

To illustrate this using the sample project, we can see that in library lib1, there is a color resource ‘color_black’ and suppose “PDF Viewer” Dynamic Feature Module wants to use that color.

In the library:

<color name="color_black">#000000</color>

Code in the Dynamic Feature that wants to use that resource.

view.setBackgroundResource(R.color.color_black)

The app will crash with exception ‘Resources$NotFoundException’ because the Dynamic Feature module cannot find the ‘color_black’ resource in its module. We can fix this by replacing the resource declaration to its full package name:

view.setBackgroundResource(com.example.lib1.R.color.color_black)

Even after we solved this one line of code, we still struggled because many resources come from the library, and we had to change it one by one. Somehow we can depend on Android Studio tools to find resources, but we still have to adjust the code manually. We finally decided to make a tool to automate this, and that helped us, especially for large modules

There is another issue when we want to override some resources from a library, especially layout resources. For example, in the library, we have a layout ‘custom_image_view.xml’ used in CustomImageView class. In the Dynamic Feature module, we want to use the ‘CustomImageView’, but we override the layout ‘custom_image_view.xml’.

The custom_image_view in library:

<ConstraintLayout …>
<ImageView android:id="@+id/image_view" … />
</ConstraintLayout>

custom_image_view in the dynamic feature module:

<ConstraintLayout …>
<ImageView android:id="@+id/image_view" … />
<TextView … />
</ConstraintLayout>

The compilation was successful, but when we launched the module, it would crash because the resource was not found. It was because, in the Dynamic feature module, the CustomImageView still pointed to the id in the library. To fix this, we need to create a class that extends the CustomImageView from the library. The solution is also given in the sample project.

From all these resource issues, currently, there is no simple way to fix it, and this is one of the big obstacles when we want to implement dynamic features.

Challenge #4: Navigation

One of the questions arises when we want to navigate to dynamic feature modules. How to navigate easily to a Dynamic Feature module without worrying about checking if the module already installed or not.

In October at Android Dev Summit, the Google team announced a library tool “Dynamic Feature Navigator”. It can navigate to the module by providing the ‘moduleName’ even in the XML layout, which is very good.

In Tokopedia, we developed a tool similar to Dynamic Feature Navigator. The concept of dynamic feature navigation in Tokopedia is to have a centralized library to map between the class to navigate and the module to be installed. For example, if we want to navigate to “com.tokopedia.hotel”, we will map this to the “hotel” module. So, whenever a module wants to navigate to a module, it just said:

navigate("com.tokopedia.hotel.HotelActivity")

Then, the navigator will map this to the hotel module. For the installation and checking process, whether the module exists or not is done in this navigator. Therefore, the caller does not need to know anything about dynamic features, even if it does not need to know if the target module is a dynamic feature or not.

fun navigate(activityName: String) {
val moduleName = mapActivityToModuleName(activityName)
moduleName?.let {
// Check if already installed
if (hasInstalled(moduleName)) {
//launch it
startActivity(Intent().apply {
setClassName(packageName, activityName)
})
} else {
//install it
startInstall(moduleName, activityName)
}
}
}
fun mapActivityToModuleName(activityName: String): String? {
// this will convert “com.tokopedia.hotel.HotelActivity” to “com.tokopedia.hotel”
val parentModuleName = getParentModuleName(activityName)
return when (parentModuleName) {
...
“com.tokopedia.hotel” -> “hotel”
...
else -> null
}
}

After all, we believe that Dynamic Feature Navigator is a great library too, and it is recommended to use. It is still a new product, but there is no harm to try.

Challenge #5: Testing

When we launched the first Dynamic Feature, there were some changes too in testing. To test Dynamic Feature, we use the methods below:

Internal App Sharing

Currently, the best way to test Dynamic Feature is through the Play Store. That is because a lot of the benefits of Dynamic Delivery rely on deferring optimized APK generator, signing, and serving to the Play Store.

Using Internal app sharing, we can upload, download, and share APK easily, and ultimately it can store the Dynamic Feature module that we need to download.

Internal App Sharing

Furthermore, to speed up this repetition process, we can use Gradle Play Publisher, which will allow us to create the App Bundle and upload it to Play Store in one click.

BundleTool

BundleTool is an alternative way to test Dynamic Features. With Bundletools, we can test Dynamic Feature offline without uploading the App Bundle to the Play Store.

We can use the below command to generate an APK from an AAB:

java -jar bundletool.jar build-apks - bundle=app.aab - output=app.apks - mode=universal"

With this testing method, we can ensure that the Dynamic Features module is installed correctly on the phone. However, we cannot simulate the Dynamic Loading because all Dynamic Feature modules will be unified into one universal APK.

Download Size VS App Size

When users download a dynamic feature module, it does not mean the app size will increase the same as the download size. The downloaded APK will be extracted on the phone, and it depends on the size of the code and resources in the module.

App size before and after DF installation

From the sample project, when users access all DF modules, the total download size is 28.76MB, and the additional app size is 36.77MB.

Uninstall When The Module Is No Longer Needed

Many of us might not be aware that we can reduce the application size not only by “download the feature on-demand”, but also by “uninstall the feature when it is no longer needed”.

For example, in Tokopedia, we have an onboarding page in which we show the introduction of application to users. This onboarding page is only shown once when users opens the app after installation.

Tokopedia Onboarding Page

In this case, Dynamic Feature comes into play. We make “onboarding” as a Dynamic Feature module. After the onboarding page appears, it is safe to uninstall, and that will reduce the application size.

Conclusion

It has been a couple of months since Tokopedia implemented Dynamic Feature modules. We faced many obstacles and errors, but eventually, we were able to solve them. That is why we think these challenges are good to be shared, especially for big applications out there.

In the meantime, the Google team also keeps improving the Dynamic Feature even more and makes it more stable. Perhaps in the future, what we present here will be changed, i.e., for example, how the testing works, how Feature on Feature works, and also the navigation. And of course, we will also evolve to those changes.

After all, Dynamic Feature brings a good impact, especially for users, because it will reduce application size at install and after installation. It is very recommended to use Dynamic Feature when an application has so many features that some of those features are better to be downloaded on-demand rather than at first install.

--

--