Modularizing at Scale: How Halodoc Adopted Android Dynamic Feature Modules
Introduction
At Halodoc, we’re on a mission to make quality healthcare accessible to everyone, everywhere—even in the most remote corners of Indonesia.
Delivering on that mission means building fast, reliable, and scalable mobile experiences. Modularization is a key enabler—by optimizing performance and footprint, we enhance reliability and deliver healthcare seamlessly across diverse devices and networks.
Being engineering-first and impact-driven, we treat performance as a first-class citizen. Optimizing startup time, runtime performance, and app size are core goals for our mobile engineering team.
As our app grew with new features, its increasing size began to affect downloads, updates, and user experience. To address this, we adopted Dynamic Feature Delivery, allowing less-frequently used features to be delivered on demand. This efficient delivery model keeps the base app lean, improves performance, and ensures users only get what they need, when they need it.
In the sections that follow, we will deep dive into the challenges we faced due to app size growth and how Dynamic Delivery helped us solve them.
The Problem: When App Size Impacts Experience
As our app evolved into a full-stack healthcare platform, its size gradually grew to nearly 40 MB, impacting install rates, update adoption, and user retention—especially in low-bandwidth regions.
Usage analysis revealed that many features were only relevant later in the user journey. Yet they were bundled in the base app, so every user downloaded and stored their footprint, regardless of actual need.
This insight prompted us to rethink feature delivery. By moving less-frequently used features to on-demand dynamic feature delivery, we kept the base app lean while ensuring users only download what they need, when they need it.
The Solution: Strategic Modularization with Dynamic Feature Modules
Our goal was not just to reduce size, but to decouple features, accelerate development, and enable flexible rollout strategies.
1) Extract Features and Isolate Dependencies
- App size often grows as new features are added, even though some aren’t needed at the time of installation but later in the user journey.
In our case, the calling feature is used only during consultations but was still bundled with every install—meaning all users downloaded and stored it, even if they never used it. With Dynamic Feature Modules (DFM), such features can be delivered on demand—only when users actually need them. This keeps the base app lighter, faster to install, and more efficient for everyone. - We extracted the entire calling feature from our monolithic Chat feature into a dedicated Call feature as a Dynamic Feature module
2) Deliver Features On Demand with Play Core
- On-demand delivery: We deliver the call dynamic feature module on demand without impacting user experience.
- We optimized the app using ABI filters and shrink rules, which let the Google Play Store deliver only the parts of the app needed for a user’s specific device. This means each device downloads only its compatible native components, keeping the app smaller and more efficient without affecting functionality.
- Added comprehensive tests for module download, entry points, and fallbacks.
Here’s a simplified view of how we structured and integrated the Dynamic Feature Module for the call feature.
Technical Implementation:
1. Create Dynamic Feature Module (DFM)
Define the call-feature module in the manifest to enable on-demand delivery and allow removal when it is not needed.
This configuration ensures the module is on-demand, removable, and excluded from base APK fusion, helping reduce the initial app size.
2. Integrate the DFM into the Base App
After creating the call-feature Dynamic Feature Module, integrate it into the base app as a submodule. This involves declaring its dependencies and registering it for on-demand installation.
Implement DFM compatibility in the base app’s build.gradle:
Define DFM as a submodule in the base app’s settings.gradle:
This setup treats the call feature as a proper submodule of the base app, ensuring that the call feature can be installed on demand without increasing the initial app bundle size.
3. Invoke the Dynamic Feature Module (DFM)
Request the Dynamic Feature Module only when required, and centralize orchestration in a single manager class.
- Use Google Play Core’s SplitInstallManager to download and install the DFM on demand.
- Centralize the orchestration logic in a single manager (DFMManager) within the base app to ensure consistent installation, telemetry, and error handling across all entry points.
- This setup also supports module-to-module communication, allowing a DFM to be downloaded or triggered not only from the base app but also from other feature modules when required
4 . CI/CD Pipeline Behavior for Dynamic Feature Modules
We already have CI/CD pipelines in place that automatically upload the Android App Bundle (AAB) to the Play Store. Since the AAB includes both the base module and all Dynamic Feature Modules (DFMs), no separate upload or configuration is needed. The Play Store automatically manages the DFMs as part of the same bundle.
If your setup doesn’t handle Play Store uploads via CI/CD, you need to upload the single base app AAB to the Play Console. The Play Store will take care of bundling and updating both the base app and its dynamic features.
Our CI/CD pipelines automatically generate Internal App Sharing (IAS) links for quick validation and testing before production release.
Challenges Faced: What We Learned the Hard Way
Testing complexity
Dynamic delivery introduces a new layer to testing — checking whether a module is installed, installing, failed, or intentionally absent.
We tested across different build flavors, ABIs, and Android versions to validate on-demand and deferred installs, retry flows, and offline scenarios.
We also verified that when the module wasn’t installed, non-calling journeys continued to work seamlessly.
- App Update Scenario Testing: A separate app version must be uploaded to Internal App Sharing, since Play Store builds use a different signing key. The challenge is that older Play Store versions can’t be re-uploaded, so a new version must be created specifically for update scenario testing.
- Testing Dynamic Feature Modules locally: When running the app directly from the IDE, the SplitInstallManager always reported the dynamic feature module (DFM) as installed. This made it impossible to test true on-demand scenarios. To accurately test DFM behavior, we needed to build the app or upload it via the internal app sharing link. Additionally, both the base APK and the DFM APK must be installed together using the command below to test APK behavior.
User Experience During Delivery
- We introduced user-facing progress indicators and fallback screens during Dynamic Feature Module downloads to ensure transparency. This approach keeps users informed and delivers a smoother, more predictable installation experience.
Conclusion
Modularizing with DFM reduced our app size and delivered tangible business and user impact—faster installs, better engagement, and wider reach.
- Play Store install size: 40% reduction in app size, improving install success and update adoption on low‑bandwidth networks.
- Smaller initial downloads and Faster updates: app download became 25% faster. This delivered a faster install and improved update adoption, especially in low‑bandwidth regions.
- Improved Play Store Listing Conversion: DFM optimization achieved 11% higher conversion rate boost from faster downloads.
- On‑device footprint: Our Base app size after install reduced by 54%, lowering storage pressure and reducing storage‑related churn.
- Reduced size directly resulted in lower uninstalls: our uninstall dropped by 52%, improving long-term user retention.
This effort reflects our commitment to building scalable technology that enhances healthcare accessibility, ensuring that quality care reaches users across every device and network condition.
References
- Overview of Play Feature Delivery
- Configure on demand delivery
- Android App Bundles and APKs
- Configure the base module
- Android ABIs - NDK
Join us
Scalability, reliability, and maintainability are the three pillars that govern what we build at Halodoc Tech. We are actively looking for engineers at all levels and if solving hard problems with challenging requirements is your forte, please reach out to us with your resumé at careers.india@halodoc.com.
About Halodoc
Halodoc is the number 1 Healthcare application in Indonesia. Our mission is to simplify and bring quality healthcare across Indonesia, from Sabang to Merauke. We connect 20,000+ doctors with patients in need through our Tele-consultation service. We partner with 3500+ pharmacies in 100+ cities to bring medicine to your doorstep. We've also partnered with Indonesia's largest lab provider to provide lab home services, and to top it off, we have recently launched a premium appointment service that partners with 500+ hospitals that allow patients to book a doctor appointment inside our application. We are extremely fortunate to be trusted by our investors, such as the Bill & Melinda Gates Foundation, Singtel, UOB Ventures, Allianz, GoJek, Astra, Temasek, and many more. We recently closed our Series D round and in total have raised around USD$100+ million for our mission. Our team works tirelessly to make sure that we create the best healthcare solution personalized for all of our patient's needs, and we are continuously on a path to simplify healthcare for Indonesia.
Co-Authors : Siddharth Thakkar ( SDE II - Android ) Mayur Hebbar ( SDE II -Android )