Clean Architecture with Android Architecture Components

Kayvan Kaseb
Software Development
13 min readNov 20, 2020

--

The picture is provided by Unsplash

As a matter of fact, writing high-quality code for high-complexity Android applications needs considerable effort and experience. Even though an application should meet the customer’s requirements, it must be flexible, testable, and maintainable. To face some challenges and problems in software development, Clean Architecture has been represented. Clean Architecture is a software design philosophy that separates the elements of a design into ring levels. Nowadays, Google has announced Android Architecture Components that includes a new set of libraries. This can be useful for designing robust, testable, and maintainable Android apps by following Clean Architecture principles. This essay will discuss some software design principles, and consider Android Architecture Components for building robust apps as well.

Introduction and Overview

Basically, writing high-quality code for high-complexity applications needs considerable effort and experience. An application should not only meet the customer’s requirements, but also must be flexible, testable, and maintainable. In the beginning of software development, software systems really did not have the same concept of a Graphical User Interface (GUI) like we have today. Then, in the 80s and 90s, more visual interfaces began to become commonplace. As a result, it did a whole new host of problems for software developers. Some questions have been created as follows: How do you manage dynamic user input? How do you keep the view state in sync with the underlying application? How can you best separate your presentation logic from your domain logic?

To address these challenges, Robert C. Martin, also known as Uncle Bob, came up with the Clean Architecture concept in 2012. In fact, Clean Architecture is a software design philosophy that separates the elements of a design into ring levels. The fundamental rule of clean architecture is that code dependencies can only come from the outer levels inward. In other words, code on the inner layers can have no knowledge of functions on the outer layers. The variables, functions and classes (entities) that exist in the outer layers can not be indicated in the more inward levels. Also, it is recommended that data formats stay separate among levels. Clean Architecture combines a group of best practices, which can build systems with the following characteristics:

  1. Separation of concerns: This is a design principle for separating a computer program into distinct sections. This means each section addresses a separate concern. A concern is a set of information, which affects the code of a computer program. Generally, three main layers can be defined in this way: 1. View layer 2. Presentation 3. Domain model.
  2. State synchronization: The idea is that you have three different states of the data, which exist in your application at any time: 1. Screen state 2. Session state 3. Server state. Therefore, if you are planning to have multiple layers in your application, you must make sure that this would be synchronized across each one.
  3. Testability: Initially, testing your software is an essential part of the software development process specially when it comes to Clean Architecture because one of the primary goals of separated components is that it can make easier to test each component. In other words, by running tests on your software consistently, you can be able to verify your software’s correctness, functional behavior, and usability before the releasing process.

Elegance in design is a lack of unnecessary complexity.

Another significant point is the simplicity of design. It means eliminating unnecessary detail and complexity can lead to elegance in design, which plays a key role in developing high-quality application.

Besides, Android is based on a small stable, cohesive set of core primitives. This allows a common programming model across a really incredibly diverse range of devices from wearables to phones, tablets, TVs, and cars. This model also gives application developers the freedom to choose whatever framework they want inside their application for their internal framework. However, some aspects of Android app development are better served by our APIs than other parts. For example, Google mentioned that RecyclerView is the better end of that spectrum. On the other hand, Activity and Fragment Lifecycles belong down in that dark shadowy place. So, they have worked continuously during these years to fix these issues such as handling lifecycle, event maintaining, view state, and persisting data. They set some goals to improve these issues as follows:

  1. Focusing on the fundamentals.
  2. Playing well with other libraries and frameworks.
  3. Having more guidance on how apps should be built.
  4. It needs to scale for real world requirements.
  5. Reaching older versions of the OS.

Nowadays, Google has announced Android Architecture Components that includes a new set of libraries. This can be useful for designing robust, testable, and maintainable Android apps. This would be started with classes for handling your UI component lifecycle and handling data persistence.

Android Jetpack and Architecture Components

Basically, Android Jetpack is set of libraries, tools, and guidance for modern Android development. Currently, there are four categories for using Jetpack, which includes: Architecture, UI, Behavior, and Foundation. In addition, Architecture Components could be classified as follows: Room, WorkManager, Lifecycle, Navigation, Paging, Data Binding, ViewModel, and LiveData.

Android Jetpack, the picture is provided by Google documents

As a matter of fact, your Android app can run on various versions of the platform because Android Jetpack components are built to support backwards compatibility. Besides, Android Jetpack is built for modern design practices such as separation of concerns, test-ability, loose coupling, Observer Pattern, Inversion of Control as well as productivity features like Kotlin integration. This would be useful for building robust, high quality apps with less code much more easily. While the components of Android Jetpack are built to work together like lifecycle awareness and LiveData, there is no need to use all of them. It means you can integrate the parts of Android Jetpack that solve your problems; whereas, you can be able to keep the parts of your app that are already working appropriately.

Android architecture components are a collection of libraries that help you design robust, testable, and maintainable apps.

Room

At Google I/O 2017, Room as an persistence library that supports an abstraction layer over SQLite to allow fluent database access while controlling the full power of SQLite was introduced. In short, Room is a robust SQL object mapping library. Because Room can address some previous problems effectively, Google highly recommends using Room instead of SQLite. Main components in Room can be categorized into three main categories:

Entity: Annotated class that represents a database table when working with Room.

DAO (Data Access Object): A mapping of SQL queries to functions. It contains the methods used for accessing the database.

Room database: Simplifies database work and serves as an access point to the underlying SQLite database.

Room architecture, the picture is provided by Google documents

For instance, after adding some Room dependencies, if you want to have a table of users, and you want every row of that table to be an instance of the user class. First of all, you must annotate your class with that Entity. Secondly, you should set the mandatory primary key. After this main step, we require a way to access the data in the database. We can be able to perform that with Data Access Object (DAO). More precisely, you must create an interface, and annotate it with Dao. Eventually, the class that puts together the entities and the DAOs is the Room database. Thus, you have to create an extra class that extends the Room database, and annotate it with Database.

Some advantages cane be mentioned in using Room for your Android projects as follows:

  • It performs SQL query validation at compile time.
  • Simplification of access to database.
  • It diminishes boilerplate codes within the application.
  • It takes care of schema generation and data manipulation in the code.
  • It integrates well with other android architecture components such as LiveData and ViewModel.
  • Ease of implementing migrations.
  • High degree of test-ability.

LiveData

LiveData is an observable data holder class, which is also lifecycle aware. For instance, if you have your UI as well as LiveData object that holds some data, which you want to show on screen. In this case, the UI observes the LiveData object. This means the UI wants to be notified for new updates. As a result, when the LiveData changes, the UI will get notified, and then the UI can be able to redraw itself with the new data. In other words, LiveData makes it easy to keep what is going on screen in sync with the data. However, LiveData checks the state of the observer for the first step before any updates. What makes LiveData different from other observable approaches is that it is also lifecycle aware. This means that it understands whether your UI is on screen, off-screen or destroyed. LiveData knows about your UI state because you passed it when you call Observe.

LiveData is an observable data holder class. Unlike a regular observable, LiveData is lifecycle-aware, meaning it respects the lifecycle of other app components, such as activities, fragments, or services. This awareness ensures LiveData only updates app component observers that are in an active lifecycle state.

Some advantages of LiveData can be mentioned as follows:

  1. An appropriate update in UI: With the help of LiveData, your application’s UI will only be changed if there is a change in the data.
  2. No crash because of stopped activities: Only that observer will be notified that is in a live state. Thus, an app crash will not be occurred because of stopped or paused activities.
  3. Proper configuration changes: If an activity or fragment is recreated because of a configuration change like device rotation, it immediately receives the latest state.
  4. Up-to-date data: The observer will have the latest data even if the observer is in background or pause state. Whenever the observer is resumed then the latest updated data will be provided to the observer.
  5. No memory leaks: Because all the observers are bound to Lifecycle objects and are cleaned up when their associated lifecycle is destroyed, there are no memory leaks.

Lifecycle-Aware Components

Fundamentally, an object is mentioned to be lifecycle-aware if it is able to identify and respond to changes in the lifecycle state of other objects within an app. Some Android components like LiveData are already lifecycle-aware. In addition, it is possible to configure any class to be lifecycle-aware by implementing the LifecycleObserver interface within the class. In fact, one of the major problems in Android development is facing with lifecycle management issues. In other words, failing to handle application lifecycles properly can cause some problematic issues such as memory leaks and some crashes. Lifecycle-aware components as a part of Android Architecture Components accomplish tasks in response to a change in the lifecycle status of another component, such as activities and fragments. As a result, these components help you create better-organized, and often lighter-weight code, that is easier to maintain and test. Furthermore, one of the main classes in this component is Lifecycle. In short, Lifecycle is an abstract class that has an Android Lifecycle attached to it. Objects can observe this state and act appropriately. To keep a track of this state, there are two main concepts that are represented as Enums: Events and State.

The picture is provided by Google documents

Some important classes in this subject can be defined as follows: Lifecycle, LifecycleOwner, and LifecycleObserver.

  1. Lifecycle: is an object that defines the Android Lifecycle and what state it is in.

2. LifecycleOwner: is an interface for objects, which have a lifecycle like AppCompatActvity or Activity or Fragment

3. LifecycleObserver: is an interface for observing a lifecycle.

ViewModel

Initially, ViewModels are objects that support data for UI components, and survive configuration changes. In fact, rotating your mobile phone is considered a configuration change. Configuration changes cause your whole activity to get recreated. If you do not properly save and restore data from the destroyed activity, you will lose that data. In addition, you will face some UI bugs or crashes. Thus, instead of storing all of your UI data in your activity, you should put it in the ViewModel. In this case, this helps with configuration changes; however, it is also a best practice for software design.

The ViewModel class is designed to store and manage UI-related data in a lifecycle conscious way. The ViewModel class allows data to survive configuration changes such as screen rotations.

Another significant issue is that UI controllers (activities and fragments) frequently need to make asynchronous calls that may take some time to return the result. Thus, the UI controller needs to manage these calls, and make sure the system cleans them up after destroying to avoid some potential memory leaks. It is clear that this issue requires a number of maintenance because an object is re-created for a configuration change, and this leads to waste of resources due to this repeated processes

ViewModel is a class that is responsible for preparing and managing the data for an Activity or a Fragment. It also handles the communication of the Activity / Fragment with the rest of the application (e.g. calling the business logic classes).

As a result, ViewModel can be explained briefly as follows:

  1. Data holder for the Activity and Fragment

2. Survives configuration changes.

3. Never references to View, Activity, and Fragment.

4. Gateway from the UI controller to the rest of the app.

Some architectural principles in Android development

In this section, some architectural principles are explained for designing an Android application:

Separation of concerns in Android

As you noticed, the most significant principle to follow is separation of concerns in your architecture. It is a common mistake to write all your code in an Activity or a Fragment. These UI-based classes should only include logic that manages UI and operating system interactions. By keeping these classes as lean as possible, you can avoid a lot of lifecycle-related issues. You should not have your own implementations of Activity and Fragment; , these are just only glue classes that show the contract between the Android OS and your Android app. The OS can destroy them at any time based on user interactions, or due to system conditions like low memory. To provide a satisfactory user experience and a more manageable app maintenance experience, you must minimize your dependency on them.

Driving UI from a model

Another key rule is that you should drive your UI from a model particularly a persistent model. Models are components, which are responsible for managing the data for an Android app. They are independent from the View objects and app components in your app. Therefore, they are unaffected by the app's lifecycle and the associated concerns. In overall, persistence is ideal for the following reasons:

  1. Your users do not lose data if the Android OS destroys your app to free up resources.

2. Your Android app continues to work when a network connection is not available.

3. By designing your app on model classes with the well-defined responsibility of handling the data, your Android application is much more testable and consistent.

Handling dependencies among Android components

You can use the following design patterns to tackle this issue effectively:

  1. Dependency injection (DI): Dependency injection allows classes to define their dependencies without constructing them. At runtime, another class is responsible for supporting these dependencies.

2. Service locator: The service locator pattern offers a registry where classes can obtain their dependencies instead of constructing them.

Google has suggested the dependency injection pattern and using the Hilt library in Android apps. At a glance, Hilt can be able to constructs objects automatically by going through the dependency tree, supports compile-time guarantees on dependencies, and builds dependency containers for Android framework classes.

Recommended Android app architecture

In short, Google has been recommending that an Android application is composed of four main layers:

  1. UI Controllers 2. View Model 3. A repository 4. Data sources

UI Controllers:

UI Controllers are Activities, Fragments and custom views. They have really simple tasks. They observe the fields of the ViewModel and update themselves. Additionally, they want more responsibility whenever user takes an action on the UI. They understand that action, and call the ViewModel to expose whatever the user wanted to do.

ViewModel:

ViewModel prepares the data for the UI, and holds it. This is where the data for the UI lives. ViewModel knows how to get that data. It usually has LiveData. If you are using RxJava or Coroutines, it is observable, or determining observables. It survives configuration changes. That is why we put the data into the view models. Besides, it is also the gateway. You can also consider it as your UI Controller only ever talks to the ViewModel to reach to the rest of application.

Repository:

Now, the ViewModel serves as a data store for your UI Controller. However, Repository saves it as a data store for all of your application. So, it is the complete data model for the app, and it provides this data with simple APIs to the rest of the application. You can have a user repository where you pass a user ID, and it returns your LiveData of users. It coordinates fetching, syncing, persisting from different data sources, or talking to your Retrofit back-end.

Data Sources:

We have our data sources like your REST API client. You might be using Retrofit, or you have SQLite storage. You might be using Room for local persistence, or you might be communicating to other Content Providers from other processes.

The following diagram represents how all the modules should interact with one another after designing the Android app:

Android App Architecture, the picture is provided by Google documents

AndroidX Test

To address the test-writing issues, Google has announced AndroidX Test recently. It brings testing as an essential part of Jetpack. Google include some of the existing libraries that you have used before, some new APIs, full Kotlin support, which allows you to write proper and concise tests. It is available on and off device.

In conclusion

As a matter of fact, writing high-quality code for high-complexity Android applications needs considerable effort and experience. To tackle some challenges in software development, Clean Architecture has been represented. Clean Architecture is a software design philosophy that separates the elements of a design into ring levels. In addition, Google has announced Android Architecture Components that includes a new set of libraries. This can be useful for designing robust, testable, and maintainable Android apps by following Clean Architecture principles. This article discussed some software design principles, and consider Architecture Components for building high-quality Android apps based on Google resources as well.

--

--

Kayvan Kaseb
Software Development

Senior Android Developer, Technical Writer, Researcher, Artist, Founder of PURE SOFTWARE YAZILIM LİMİTED ŞİRKETİ https://www.linkedin.com/in/kayvan-kaseb