As Android evolves, developers become more focused on creating applications with good architecture. Good architecture helps reduce development time, makes the code easy to understand, maintains code effectively, and makes it testable. In this article, we will discuss the recommended architecture for Android development, known as Clean Architecture, and some good practices for building Android apps.
The Clear Architecture includes three layers: the UI Layer, Domain Layer (optional), and Data Layer.
The UI Layer, or presentation Layer, is responsible for interacting with the UI and includes Activity, Fragment, and ViewModel. These components communicate with the domain and data layers to retrieve data and update the UI.
The Domain Layer is an optional layer that sits between the UI and Data Layers. It is responsible for encapsulating complex business logic or simple business logic that is reused by multiple View Models. Also, this layer is independent of any other layers which means that changes in other layers will have no effect. The components of the Domain Layer include Models, which define the core structure of the data used within the application, and Repositories, which are interfaces used by the use cases and implemented in the Data Layer.
The Data Layer contains the business logic of the app and the implementations of repositories declared in the Domain Layer. The components of the Data Layer include Models, which includes Dto and Entity models, Mappers, Network, Cache and Data Sources. The Dto model defines POJO of network responses, while the Entity model defines the schema of the database. Regarding the Mappers, it transforms Data between domain, dto and entity models. The network handles the operations. The cache is responsible for performing caching operations. And Data sources is responsible for deciding which source (network or cache) will be used when fetching and presenting the data to View Models.
Best Practices for Building Android Apps
“Programming is a creative field, and building Android apps isn't an exception. There are many ways to solve a problem; you might communicate data between multiple activities or fragments, retrieve remote data and persist it locally for offline mode, or handle any number of other common scenarios that nontrivial apps encounter. In most cases following them makes your code base more robust, testable, and maintainable”
1. Don't store data in app components
Avoid designating your app's entry points, such as activities, services, and broadcast receivers, as sources of data. Instead, they should only coordinate with other components to retrieve the subset of data that is relevant to that entry point.
2. Create well-defined boundaries of responsibility between various modules in your app
Do not define multiple unrelated responsibilities, such as data caching and data binding, in the same class.
3. Expose as little as possible from each module
Don't expose an internal implementation detail from a module to gain a bit of time on the development.
4. Focus on the unique core of your app so it stands out from other apps
Avoid writing the same boilerplate code again and again. Use Jetpack libraries and other recommended libraries to handle the repetitive boilerplate.
5. Consider how to make each part of your app testable in isolation
Don't mix the logic of Network and Cache modules in one place. If modules are well-isolated, it’s easier to test each part effectively.
6. Persist as much relevant and fresh data as possible
Persisting data allows users to use the app’s functionality even when the device is in offline mode.
Modularization is the process of organizing a code base into loosely coupled and self-contained parts. Each part is a module, and each module is independent and has a clear purpose. There are many advantages to modularization, including:
Build time: Only the change modules and the modules depend on changed ones will be built.
Reusability: Modules can be used across apps.
Customizable delivery: Play Feature Delivery allows certain features of the app to be delivered conditionally or downloaded on demand.
Test: A testable code is one where components can be easily tested in isolation.
Encapsulation: Each part of the code should have the smallest possible amount of knowledge about other parts.
Common patterns in modularization include:
Data modules: A data module usually contains a repository, data sources, and model classes.
Feature modules: A feature is an isolated part of an app's functionality that usually corresponds to a screen or series of closely related screens.
App modules: App modules are an entry point to the application. They depend on feature modules and usually provide root navigation.