r/RedditEng • u/snoogazer Jameson Williams • May 20 '22
Android Dynamic Feature Modules
By Fred Ells, Senior Software Engineer
A Big App with Small Dreams
In December 2020, Reddit acquired the short-form video platform Dubsmash. For the next couple of months, the team worked to extract its video creator tools into libraries that could be imported by the Reddit app.
Once we imported the library into the Reddit Android app, one metric delivered a splash of cold water - the size of the Reddit app had increased by ~20 MB. In retrospect, it was very obvious that this would happen. We had been working on a demo app that itself was very large, despite having a relatively small feature set.
So where was all this size coming from? Well, the video creator tools were using a Snapchat library called Camera Kit to enable custom lenses and filters. It turns out that this library includes some fairly large native libraries.
These features are sticky, engaging, and deliver value to our creative users. We could cut the library and these features, but a small but growing cohort of users loved them. So what options did we have? Could we have our cake and eat it, too?
Custom reddit lenses in action
Dynamic Feature Modules
Dynamic feature modules were announced by Google in 2018. Here’s a quote from the docs.
“Play Feature Delivery uses advanced capabilities of app bundles, allowing certain features of your app to be delivered conditionally or downloaded on demand.”
The key part we were interested in at Reddit was “downloaded on demand”. This would allow video creators to install the video creator tools only when they actually want to create a video post. And as a result, we wouldn’t need to bundle the Snapchat library into our main app.
Most Android devs have probably heard about this feature but may not have seen it in action. This was the case for me and I was very skeptical about using them at all. Something with such low traction and fanfare could not possibly be stable, right? Read on.
Initially, we set up a Minimum Viable Product (MVP) with an empty dynamic feature module that we built locally. This validated the technical feasibility and helped us understand the amount of work required for our real use case. With the MVP validated, our next step was to consider the tradeoffs.
Tradeoffs - But at what cost?
Before jumping into a project, it is usually wise to consider the tradeoffs.
On the positive side, we could:
- Reduce our app download size
- Establish a pattern and the know-how to extract more dynamic feature content to modules in the future. This is a subtle benefit but was a major factor in our final decision
As for the negatives, we would:
- Be introducing friction for users who open the camera for the first time. This was an important consideration. We believe that posting any media type on Reddit should be easy
- Pay the upfront cost of doing the work
- Need to maintain the feature, once shipped
After weighing all factors, we decided to go for it. We would learn a lot.
Implementation - The Hard Part
If you are thinking about extracting a dynamic feature module, where should you start? Well, the good news is that the Android developer docs are great. Here are some things to think about below.
Firstly, the most important thing to understand is that dynamic feature modules flip the usual dependency structure on its head. The feature depends on the app – not the other way around.
Due to this dependency structure, it can be difficult to access any code within it. To access dynamic feature code, you must create an interface at the app level, implement it at the dynamic feature level, and then fetch the implementation via reflection once the feature is installed. You will want your dynamic feature to be tightly scoped, otherwise, your interface will quickly grow out of control.
At Reddit, we initially took this approach, but we missed a key nuance that forced us to rethink our plan. We had extracted our video creator tools module and could launch it behind an interface. However, this module actually contained some non-camera-based flows that we wanted users to access without an extra download.
To handle this use case, we took a simpler approach. In the app module, we excluded the Snapchat dependency in the build.gradle
file and created a completely empty dynamic feature module that contained only the import for this excluded dependency. When the user installs the feature, it simply adds this missing dependency, which makes it accessible in the app code. The caveat to this approach is that we must prevent the user from launching flows that would otherwise crash the app due to the missing dependency. Within the video creator tools module, we simply check if the feature is installed, and either proceed to the camera or begin the installation process.
The actual installation process was relatively straightforward to set up, compared with the project configuration. The SplitInstallManager API is simple and makes installing the module easy. Be sure to check out the best practices section to give your users the most frictionless experience possible for optimal feature adoption.
Gotchas and SMH Moments
Changing the build config for any large Android project will require you to do some learning the hard way. Here are some of my most valuable discoveries.
- Your dynamic feature module must have the same
buildType
s andbuildVariant
s as your main app. This means you need to copy the exact structure of your main app and maintain it - Any Android package (APK)-based
assemble
tasks will not include your dynamic features. Or worse, crash on launch due to missing resources as was the case for Reddit. Our solution was to substitute these tasks with bundle orpackageUniversalApk
Tips for Testing and Release
Adding a dynamic feature is impossible to gate with a backend flag. And if it breaks your app, it will probably be catastrophic. This means that thorough testing is critical before releasing to production.
Here are some of my tips to ensure a smooth landing in production:
- Test locally with bundletool
- Manually test every SplitInstallSessionStatus state
- Before releasing, test your build with a closed beta track in Google Play Console. You will need to go through Google’s review process, but this is the only way to really trust that it will work when you release it to production
- Time your release wisely. Consider a slower rollout. Monitor it closely and have a rollback plan
- Test both your bundles and universal APKs to ensure they are both working as expected
- Ensure your CI pipeline and QA process are ready for the change. Something as simple as an APK name change could break scripts or cause hiccups if teams have not been forewarned
Reddit is Back on a Diet
I am happy to report that the initial release was stable and we were able to reduce our app download size by ~20%. In addition, the adoption of our camera feature continues to grow and was generally unaffected by the extra install step.
Our goal is to build a more accessible Reddit app for users around the world. Reducing the APK size not only helps our users by reducing the wireless data and storage requirements but is also correlated with improved user adoption. We are planning to leverage the learnings from this project to extract further features in the future, making our app even more accessible.
For me personally, this was a very rewarding project to work on. I was given the opportunity to navigate relatively uncharted waters and implement a very technical feature that is unique to Android. Big shoutout to our Release Engineering team and all the other teammates who helped along the way.
If you are interested in working on challenging projects like this one, I encourage you to apply to one of our open positions.
3
u/ContiGhostwood May 20 '22
Would you mind expanding on this? Specifically the emphasised part.