Preparing your project for Dynamic Features

This series does assume that you know a little bit about what dynamic features are, and how that affects your gradle dependency graph (i.e. features can now depend on your application module). If you are not sure about what dynamic features are, you can read about it in the documentation here.

Where We Started

In the start of this post I said our starting point was a single module, monolithic project. That’s not quite true, by January 2019, every feature in the app had been migrated to clean architecture and the project was modularised horizontally, by architectural layer:

January 2019 — Modularised by architectural layer

While this was slightly better than a single module, as most features were pretty clean and decoupled, it wasn’t helping our build times. :app was still a huge module and it was taking forever to build thanks to a pretty large Dagger graph being non-incrementally processed by kapt.

Our initial feature modularisation had a primary goal of reducing build times, and if we could get an instant app out of that, that’d be a huge bonus.

Google’s open source Plaid app was of particular interest to us, it had just gone through a re-architecture to dynamic features modules, and served as very good source for how to achieve the same in our project.

Ben Weiss, one of the collaborators on Plaid, has written two very useful articles about Plaid’s re-architecture, one about migrating to dynamic features and the other about DI in a multi module project. The latter is what we will be focusing on in this post. CWC19 already had a very large and established Dagger setup, and it quickly became clear that it wasn’t going to work in a project with dynamic feature modules. What follows is the steps we took to migrate the project from a huge monolithic Dagger Android setup to something much closer to Plaid’s DI setup.

Step 1 — Creating a base application module

The first thing we realised is that we couldn’t use :app as our base application module for dynamic features. We could have just moved the Quiz feature into a dynamic feature module that depended on :app , and this would have let us do on demand delivery for the Quiz feature pretty easily. However, that wasn’t going to help us deliver an instant app. At this point we were still shipping ICC as an APK, and it was 56MB. There was zero chance of shipping Quiz as a 4MB instant app if it depended on the :app module.

The next step was pretty obvious then, we would create a new :base module that would become our application module, both :quiz and :app would depend on that, and :app would also become a dynamic feature module that was always installed at install time. The plan was to end up with a gradle dependency graph that looked something like this:

Initial plan for dynamic feature module setup. :app and :quiz are dynamic features that depend on a much smaller :base application module.

Note: Google’s samples suggest that your ‘core’ app experience should live in your base module. This is very difficult to do when you are chasing the 4MB instant app limit. As of October 2019, the only feature we have in our base module is the splash screen.

We realised that turning :app into a dynamic feature module would probably cause its own problems, so before we committed to that, we made :base an android library module, and started moving stuff that we knew that would have to live in :base .

First step towards migrating to dynamic features, creating a :base module that will eventually become the application module.

At the very minimum, the application class would have to be moved to :base . Both :app and :quiz were going to need it, so it made sense to move that to base first. This is where we encountered our first big hurdle.

Step 2— Moving your application class to :base

At this point our custom application class looked something like this:

IccApplication class before we moved it to :base

The problem isn’t obvious at first glance. Both Timber and Fabric will be useful in dynamic feature modules anyway, so those dependencies can go in to the :base module with no issue. The problem lies with the fact that IccApplication is a DaggerApplication and it is the one creating our AppComponent . ICC at this point relied heavily on Dagger Android, and Dagger Android encourages you to set up an AppComponent that knows about pretty much everything.

AppComponent before we migrated to dynamic features

AppComponent included ~90 modules of its own, and even more with all the features using @ContributesAndroidInjector to add to SubComponents to this Dagger graph.

This meant that if we were to move the Application class to :base , we’d also have to move AppComponent , which would mean we have to move pretty much everything into :base , completely defeating the point of creating a small :base module in the first place!

Adopting Plaid’s DI setup would be ideal here. :base should provide a much smaller CoreComponent instead of the mammoth AppComponent , and each feature should have it’s own Component. Unfortunately, it wasn’t that straight forward in our project. Everything in :app is setup with Dagger Android, so features rely on AppComponent to be able add their Subcomponents to the graph using @ContributesAndroidInjector . We were 3 months out from the World Cup starting, migrating every feature away from AppComponent to use their own Component (like in Plaid) was not feasible.