Primarily a health & fitness app, cure.fit features multiple products/services in a single app i.e. cult.fit, eat.fit, mind.fit, care.fit, and live.Fit. However, this comes with the responsibility of implementing tons of features — many of which require different SDKs and external libraries which could ultimately bloat up the app codebase and drastically increase the app size.
App size can directly impact the install rate and performance which are two major factors that determine the success of a venture.
According to the studies conducted by Google, decreased app sizes results in significantly increased app install rates.
Thus, this happened to be one of the alarming problems that needed to be addressed through a scalable method for cure.fit to succeed and become user-friendly.
Here’s what we did
To deal with these issues, we decided to adopt Playstore’s Dynamic Delivery Feature which was brought into existence after Google introduced App bundles. In the upcoming section, let’s take a look at how the dynamic delivery feature works.
This snippet from Playstore documentation exactly specifies what Dynamic Delivery is all about.
A unique benefit of Dynamic Delivery is the ability to customize how and when different features of your app are downloaded onto devices running Android 5.0 (API level 21) or higher. For example, to reduce the initial download size of your app, you can configure certain features to be either downloaded as needed on-demand or only by devices that support certain capabilities, such as the ability to take pictures or support augmented reality features.
Although you get highly optimized downloads by default when you upload your app as an app bundle, the more advanced and customizable feature delivery options require additional configuration and modularization of your app’s features using dynamic feature modules. That is, dynamic feature modules provide the building blocks for creating modular features that you can configure to each be downloaded as needed.
When it came to cure.fit, we needed to modify & configure both the base module and the dynamic feature module.
However, the big question was — Which feature should be made dynamic?
We had to choose a feature that had the least impact on the end-user and which also contributed to the maximum increase in the app size. Being pretty core to our app and the most used features such as the login or the class booking feature could never be made dynamic. So we narrowed it down to the Video-Calling feature. Only used by a user to book online consultations with a care.fit doctor, or to book a one-to-one therapy session through Mind.Fit, this feature contributes to around 5+ MB of the app size. The video calling feature is only used after an appointment is successfully booked.Despite the multiple products/services and features, our app offered we didn’t have to spend a significantly long time refactoring the codebase since our codebase was already largely modular
How did we make it a dynamic delivery feature?
1.Create a modular codebase
We started off by establishing a modular codebase for every feature set and each business (cult.fit, mind.fit, eat.fit etc) of the app. With this principle in place, we created a separate module for the video calling feature. Despite the multiple products/services and features our app offered we didn’t have to spend a significantly long time refactoring the codebase since our codebase was already largely modular.
2. Configure the base & feature modules
The next step was to configure the build.gradle files of the base and the feature modules. This was fairly straightforward to follow from the documentation. We had to add dynamic modules in the base module’s build.gradle and apply plugin in the feature module’s build.gradle.
Apart from that, we also had to configure androidmanifest.xml of the Feature Module:
The most important configuration here is the <dist:delivery> tag. Here we are presented with two different options.
- <dist:on-demand> : Feature will be downloaded dynamically on demand.
- <dist:install-time> : Feature will be available right from first installing the app.
The primary difference between a dynamic feature module and a normal module is that a normal module is added to the base module; whereas, for a feature module, the base module gets added to the feature module as a dependency.
This simply means that we don’t have access to classes and resources of feature modules in the base module. Why? Because the feature is delivered dynamically on-demand and it can’t be guaranteed whether those classes will be available in the application during the time of installation.
Built on ReactNative, our video-calling feature leverages ReactPackage for different ViewManagers. Hence, we were to declare a ReactPackage in the base module so that it can be loaded into the Application start. This brought forth a unique problem since we don’t have access to the classes of feature module in the base module. In order to solve this, only a few approaches could be taken:
- Reflection call
- Leveraging the ServiceLoader
- Integrating with Dagger2
For our use-case we just needed direct access to the custom view classes from the feature module for ViewManagers, hence we used reflection calls.
3. Making the feature available for download at the right place & the right time:
Another important aspect to consider is prompting users to install the dynamic feature at the right time in the user journey. When it comes to the video-calling feature, we ask our users to install the feature only after a successful video consultation booking.
Note: The Dynamic Delivery feature doesn’t ask for a user’s consent if the feature is less than 5 MB.
What we love about the Dynamic Delivery feature
- It works effortlessly with App bundles. The base module can be easily configured by doing minimal changes to the build.gradle. Furthermore, configuring a feature module isn’t difficult to achieve even when working with a ReactNative app.
- It encourages a modular way of writing code which is important for developing any new feature. Teams can independently work on a feature module without having to worry about the base module. Modularisation of the app comes with additional benefits.
- The documentation is straightforward to follow and the sample app offered is quite useful.
- Adding a new feature module in the future can be done through configuration in build.gradle files and binding action for downloading the feature and native module for ReactNative can be easily written.
Challenges we faced while implementing the Dynamic Delivery feature
The local setup did not allow feature downloads. We had to upload an app bundle on the internal test track, alpha, or beta track on the Playstore to develop a feature which takes significant time to upload and verify. Although, just recently Playstore has fixed this problem and we can locally develop and test for on-demand modules with FakeSplitInstallManager which was introduced with Play Core 1.6.4.
We have also done a few other optimizations for improving the app size. First and foremost, all images that we bundle within the app must be compressed properly. So we wrote a script that uploads and optimizes png files using tinypng.com. We removed all unused resources from the app. Furthermore, removing unused or dead code also helps. These are a few minor but impactful optimizations that helped us reduce our app’s size.
Using bundle instead of apk for Android also helps reduce the app size. Proguard is also a very good code-optimizer that improves app size by a good extent.
Since our app is built on reactNative, all the optimizations that we do on JS will apply for both iOS and Android. We are also working on optimizing the JS bundle.
Currently, we are planning to leverage dynamic delivery feature for Pose-Estimation feature which includes TensorFlow-Lite Lib & model file for machine learning. This will provide us with a 5+ MB gain in-app size which will eventually give us a 30% reduction in app size from 36+ MB to 25+ MB.
At cure.fit, with a rapidly growing user base and growing scale of businesses and products, we will be able to add new features without impacting the app’s size or performance.
PS: We’re hiring! Write to us at email@example.com.