Instant Apps: Lessons Learned
Instant Apps lead to more installs and higher engagement: they offer Android users a taste of Tiqets as a native app, without having to install it.
Native Android apps can provide a better mobile experience than the web. If that weren’t the case, I’d have no job.
There are more ways that an Instant App can be accessed, but our main uses case are launching it, instead of our website, from search results or when browsing our website:
Luring users that are searching the web for what to do during their next holidays into experiencing our Android App, seamlessly and before even installing it, always sounded like a great idea, UX and commercially-wise. Yes, the situation is dire right now, but people will, someday, travel for holidays again!
Here’s a story of our attempt at implementing an Android Instant App at Tiqets.
Google recommends implementing Instant Apps using Dynamic Feature Modules.
We didn’t want to halt our releases and focus solely on the refactoring and re-structuring needed to implement our Instant App. Instead, we planned a three-step process, during which we could keep providing new features and bug fixes to our users without granting anyone but our developers access to the work-in-progress Instant App.
Step 1: Proof of concept
Our proof of concept was a fat Instant App (fat because it included the code of the full app), without any custom Instant code. This Instant App would launch when the user opened a link to any of our products.
To build it, we converted the App into Dynamic Feature Modules. We kept all existing code in the base module, and we configured two feature modules:
- Installed: a module with “At-install delivery,” containing the app’s entry point URLs defined in its AndroidManifest.xml, except for those specified in Instant.
- Instant: a module with “Instant delivery,” only containing the Instant App entry point URLs defined in its AndroidManifest.xml.
Step 2: Modularization
Instant Apps used to have a size limit of 4MB. But Google seems to have silently increased that limit to 15MB. Even if your full App is under that limit, it is still a good practice to make the Instant App as small as it can be.
To reduce the size of our app, we moved all non-instant functionality out of the Instant App and into “At-install delivery” dynamic modules. These dynamic modules would be available at install time for the Installed App.
For more details on this approach, see Patterns for accessing code from Dynamic Feature Modules (of which we used the Reflection approach).
We used Component Dependency for dependency injection encapsulation instead of the regular Dagger Subcomponents. The feature components specified their dependencies via an interface, which would be implemented by the App’s Main Component.
This approach worked like a charm for modules without UI or much access to resources, like Push Notifications and Leanplum (our tool of choice for CRM), but had a fair share of issues for modules with some UI, like Checkout, which uses Adyen’s Drop-in solution for payment processing.
For styles, we needed to work around the Manifest merger by specifying placeholders for styles from modules in the base one. This wasn’t pretty, but a minor hassle.
Step 3: Instant-specific code
After making the Instant App small enough, we planned to add the “polish before publishing” to the Instant-specific code:
- Add Call-To-Install the App when trying to make a purchase.
- Remove access to parts of the app that don’t make sense in the Instant version, such as wallet, settings, or profile.
We never reached this step.
Why didn’t it work?
All the theory, technical specs, and frameworks seem to fall into place harmonically. But during execution, we were surprised by many caveats that made us decide to roll it all back and try again later.
Here’s what went wrong.
1. Universal APKs
Universal APKs were our biggest drawback.
Modularizing UI code worked (kind of) fine for App Bundles (to be used by the Play Store). However, two tools we use don’t work with Dynamic Feature Modules in App Bundles:
- Firebase App Distribution
- Firebase Test Lab: this one does support AAB files, but it won’t install “At-install delivery” modules.
Styles were also a problem. We needed to specify all the styles from feature modules in the base module. That means all styles, even those used by dependencies of dependencies (e.g., Adyen, which uses Adyen-3DS), some of which are closed-source. This adds complexity and risk of having an unstable app: we’d need to keep track of styles inside dependencies (and their dependencies) if we ever wanted to update their versions.
2. Resource shrinking
Resource shrinking is an invaluable tool for keeping your App size in check — but it does not work for feature module apps. There is no ETA for a fix yet, all we know is that it will be fixed after Android Gradle Plugin 4.1 (4.0 isn’t even out yet).
Imagine all the unused resources from big dependencies that you have, but of which you use only a fraction of the features. Resource shrinking will get rid of them. Same thing R8 (or Proguard, if you’re old-school) does for the unused code in your app, but for resources.
3. Unused resources
If a project has unused resources, that’s typically considered an error. Just like unused code might add confusion and difficulties to refactor your app, keeping unused resources around will only do your project harm.
This doesn’t work either for Feature Module Apps yet. It will be fixed on Android Gradle Plugin 4.0 (to be released with Android Studio 4.0).
4. App resolving system dialog
We encountered a bug with the Application Chooser Dialog. Launching Activities in an App with Dynamic Feature Modules via Intent Filter is something we do a lot. When we do this, the Application Chooser Dialog shows up — even though our app is the only one that could handle that request. Although it resolves itself quickly on its own if coming from outside the app (from a deep-link — we have many of those), it’s still visible to users.
We decided to pull back on having an Instant App for now. Instant Apps are full of potential, but they weren’t worth the current hassle for us.
This is the summary of the issues:
- Universal APKs: Fixed!
- Resource Shrinking: Hopefully fixed after AGP 4.1, but nothing confirmed.
- Unused Resources: Fixed in AGP 4.0!
- App Resolving Dialog: Acknowledged, but no ETA for a fix.
Since issue #4 isn’t functional, but a nuisance, we could check again when AGP 4.0 or 4.1 get launched if we can try to make an Instant App again.
We’re hoping that this piece will end up being the first of a multi-part article with a happy ending.