Android Architecture Deep Dive — Part 1 — Project Layout
What follows is an adaptation of multiple projects I’ve worked on over the past year. After working as a web developer and project manager for a year I returned to Android in August of 2019 to a very different landscape than when I left.
Fragments won the war against activities. The reliable MVP pattern had been dethroned by MVVM. Now we had four different ways to access views (and strong arguments on each side of why there’s was the best). Add in LiveData, Coroutines, Navigation, Room, and a hundred other libraries and suddenly apps looked nothing like they did before.
If this is your first expedition into the new state of Android, I highly recommend checking out the excellent Udacity course from google. We require all our apprentices to watch it and a lot of senior developers are finding it helpful too.
While this video provides an exceptional intro into modern android development, it doesn’t do a great job of talking about how an app should be structured. Also, some of these libraries can feel awkward at first in production code without a few pieces of helper libraries that the community has been developing.
Note: This work has been distilled from countless hours of development all across Accenture Digital Products. I want to specifically call out Glen Daniels, David Zou, Anton Spans, Evan Denerley, Justin Guan, Ahmed Aly, Matt Groves, Paula Basswerner and Phil Corriveau. This was truly a team effort and I hope this serves to accurately document everything we’ve accomplished this year.
Structure: Multimodule
Traditionally, your android app probably looked like this:

This is essentially a single module android project. Everything lives in the app module. You probably have multiple packages inside “app”, but that’s the only module as far as Gradle and the rest of the build system is concerned. However, Android Studio now has great support for multimodule projects, and there really isn't a good excuse for not using them.

So what’s the big deal with modules? Now we’re chunking the app into numerous smaller modules that have distinct and clear dependencies. When you create three packages inside a single module, you run the risk of circular dependencies and creating tightly coupled components. This makes refactoring difficult as everything depends on everything else. In multimodule, refactors can be targeted at single modules, one at a time.
Build times are also faster while working in multimodule. In the diagram above, if you made a change to the “networking” module but no other module, you only need to rebuild the networking module to see the changes. A full clean/rebuild will still build everything fresh, but this is helpful for developer time.
The appropriate structure for multimodule will really change depending on the project, but we follow a general structure that has been fairly repeatable:

In the chart above, we break modules up into four “tiers”. Modules at the top can import any combination of modules from a tier below theirs. However, no module can import anything from a module above their tier. In some cases, modules will need to import from the same tier as well, but that should be evaluated on a case by case basis. Multimodule does not allow cyclic dependencies, so this hierarchy is important to get right.
App — At the top of the tree, we have the app module. This is the root of the application and where the app itself is kicked off. We have been strictly following a single activity pattern, so this is where our MainActivity file lives. We also keep the android manifest here, and the main navigation graph. If any of these concepts sound unfamiliar don’t stress out too much as we’ll cover them all later.
Feature — Below the app module, we have our “feature” modules. These should be based on the actual business needs of the project. Think things like “Onboarding”, “Login”, “Shopping Car”, “Profile”, etc. The titles of the feature modules should be terms your product owner would generate or at least understand. These modules should encapsulate singular flows that map to business structure. If a product owner wants to change everything about the shopping experience, we want the code to be modularized in such a way that only the one module needs to change, while the others stay static.
Core — Core modules are the ones we expect to share across multiple feature modules. Some examples are CommonUI, Networking, and Database. CommonUI should contain any reused custom views, colors, dimensions, and strings. We also typically keep our BaseFragments/BaseViewModels here so all features can access them.
Our networking and database modules are sometimes combined into one single module, but the concepts remain the same. Any base Room or Retrofit classes and utilities should go in here. We typically recommend that these modules remain light, and that actual implementations of services go in the feature module that consumes them. For example, if only the Login module is going to use the LoginService, it likely belongs in that module and not the Networking module. This keeps the logic for a given feature all in one place and prevents unneeded coupling across modules.
Pure Kotlin — These are the lowest tier modules, and typically the simplest. These are modules that don’t rely on the Android Framework at all and are pure Java/Kotlin modules. We don’t have too many of these, but the main ones typically are wrappers around Logging and Analytics Libraries. This is also the layer our Dependency Module will sit at.
Team Structure and Multirepo
Another important benefit of Multimodule is that it lends itself well to working with a larger team. Once you start working with 3+ developers, everyone touching the same codebase can start to feel difficult, especially at the start of a project where everyone is touching the same parts of the codebase. With feature modules, teams can be broken up into pods of a handful of developers that have an independent space to work in.
If the timeline allows, I highly recommend giving 1 month of ramp-up time to a small team of developers to set up the core modules and define demo examples of how the features should be built. This reference structure will create a base for how the rest of the pods should be working together.
With multimodule, you also have a choice between single or multiple git repositories. In the multirepo workflow, each feature team would have their own repository to store the code for their module. All modules would need to be built using a service such as Artifactory. Individual Modules would then pull in their dependencies similar to how a normal library is imported into Gradle.
A word of caution, this is a very important decision for your team. Multirepo has major benefits in that you can ensure only authorized changes are made to a particular repo. However, this means any change that requires refactoring in more than one module requires numerous PRs and approvals to get working. The developer can get around this while debugging using Composite builds, but the PR process can be long and complicated.
Multirepo also means every module needs a sample app to test its codebase. You can’t confirm a custom view in CommonUI is working if it is used in a completely separate repo. The sample apps live in the feature modules but do not get built on artifactory.
My official recommendation is to not use multirepo. A solid PR process with CI/CD to build and test PRs is much better at protecting against unwanted changes than a multirepo setup. This allows refactors to be agile and transparent to other teams. After a year of development, the code should be far more stable and then you can consider if multirepo is a good idea. Multimodule allows you to move the modules out to their own libraries at any time.
Keep following along with Part 2 where we talk about MVVM: https://medium.com/@jdmccormack63/android-architecture-deep-dive-part-2-5aea152eecef