Android Architecture Example: Layers and Modules

Juan Peretti
The Startup
Published in
4 min readJul 22, 2020

--

Introduction

Since the very early releases of Android until today, the architecture of Android applications has evolved to support production-quality apps that can be developed with robust designs, testing capabilities, and maintainable code. This article explains how the architecture of an Android application can be seen in two different dimensions: the logical dimension and the modularized dimension.

The article and the explanations are given based on MoviesPreview, which is an application that allows a user to browse the data exposed by The Movie DB API to see different information about movies, actors/actresses, and some other features. The full code of the project can be found in the following link:

The Logical Dimension

I decided to name this dimension the Logical Dimension since it explains the architecture from a logical point of view: what is the logic that each layer of the architecture is containing.

From this dimension, the application is architected in three different layers, sitting one on top of the other: the View Layer, the Domain Layer, and the Data Layer.

Logical Dimension
  • The View Layer has the responsibility of rendering the view state of the application. The view state of the application is the result of the execution of the application’s use case and its adaptation to ‘UI Language’. A detailed explanation can be found in this article.
  • The Domain Layer has the responsibility of executing the logic encapsulated in the application’s use cases. Each Use Case represents a feature that the application provides to the user. More information can be found here.
  • The Data Layer takes care of abstracting the upper layers of the different data sources that can provide information to the application. This Medium article contains a detailed explanation of the repository pattern used in this layer.

All three layers manage a common language: the domain classes, which is the actual representation of the entities that are part of the business model of the application. At the same time, layers at the top and the bottom of the image ‘speak’ their own language: the View Layer speaks View State, the Data Layer speaks Data Classes. This is because the implementation of each layer has different dependencies from frameworks and platforms. Each layer takes care of translating or adapting the domain language to its own domain.

One key aspect of this layered architecture is that the domain layer knows nothing about the platform: it is agnostic of the fact that the application is running in an Android platform, making it portable to other platforms. This can be even clearer in the fact that access to specific platform features or implementations (like Context access or connectivity detection) is found in the top layer or the bottom layer. The Domain Layer has no dependencies on these kinds of things.

The Module Dimension

I use this name to define a dimension that analyzes the architecture of an application from the Gradle modules perspective and the relations between those modules.

As described in this Google I/0'19 presentation, modularization is a software design technique that helps separate functionality in independent modules, allowing the modules to focus on specific functionalities. Using this software design technique, you get a great number of benefits (scalability, maintainability, faster compilation, the potential for Dynamic Features, etc).

I decided to follow a mix between feature modularization and layer modularization in which each layer described in The Logical Dimension is narrowed to a specific module but also each feature is contained in its specific Gradle module:

:app : this is the application module, that contains the main Activity along with other features that are bonded to the home screen. It depends on:

  • :features / to be able to redirect to each specific feature from the home screen.
  • :mpdomain / the home screen executes Use Cases. Therefore, it needs to have access to the domain module.
  • :mpdata / it depends on this module, but not directly. This means that it is not consuming code from the module, but it needs the code in there to inject dependencies since the Dependency Injection framework (Dagger) is located in the :app module.

:features : this is actually the representation of the conjunction of all modules that contains the features of the application. It depends on :mpdomain to be able to execute the Use Cases that support each feature.

:mpdomain : contains all the domain objects definition (the model) along with the Use Cases and the repository definitions.

:mpdata : contains the implementation of the repositories defined in :mpdomain and the data sources implementation to access the data needed by the application.

:mpcommon : this is a common module used to contain code that is common to the entire application (like date-utilities). All the application’s modules have a dependency on this module.

:mpdesign : this module contains code that is specific to the UI of the application, such as custom views and extension functions related to views. It also contains all the resources that are used by the application, like styles, colors, dimensions, etc. All modules that have UI code are depending on this module.

:mptestutils : this is a special module that contains utilities that are specific to Unit Tests, like JUnit5 extensions. This module is not present in the graphic above for simplicity, but all other modules of the application have a dependency on this module to be able to execute Unit Tests.

--

--

Juan Peretti
The Startup

Mobile Software Developer — Passionate traveler