Contact us
Contact
Blog

Development

9 min read

Getting Started With Clean Architecture for Android [Part 1]

David
David
Android Developer

Talking to my fellow Android developers, there was always one book that would come up in our conversations – Clean Architecture: A Craftsman's Guide to Software Structure and Design by Uncle Bob. To see what the fuss was all about, I gave it a try a few months ago. And guess what? I'm a fan now too.

To be completely honest, I've never come across Clean architecture before. This book inspired me so much though that I decided to develop an app which follows its principles. In the end, the best way to illustrate what Clean Architecture looks like in a project is by going over an example app. But, keep in mind that this is only my book interpretation and it may differ from yours. Nothing wrong with that – in fact, I'd love to hear back from you once you finish the article.

Clean architecture

My main misconception with Clean architecture was in thinking of it as another architectural pattern like MVP or MVVM, which I can apply in the same way regardless of the requirements. But it's actually a set of guiding principles that different teams can interpret in different ways. For example, you can recognize MVP and MVVM whenever they’re present because they follow a specific pattern – there'll be a repository of some sort (Model), a presentation layer (Presenter or ViewModel), and a View. You’re supposed to fit the app's requirements into these templates. The main draw of these patterns is that they unify the way in which the code is written within a team, which makes it much easier to develop, upgrade and fix.

Clean architecture extends these patterns mostly by changing the way we construct the Model layer. In fact, it applies a whole new architectural pattern to it. This is the most important layer of your app because it contains its business rules, which define the unique way your app generates output data from a user’s input.

Just a look at this layer’s directory structure should convey what the app does. Uncle Bob compares this to looking at blueprints of buildings. When you see the floor plan of a house, you instantly know it’s a house. It has a living room, a couple of bedrooms, and a kitchen  – its purpose is to be a living space. Similarly, the way you organize your code and name your high-level classes should convey the app's purpose.

### Sample app
Now, let’s get down to business. For this purpose, I created a Shazam-like music recognition app with the following requirements:

1. Recognize songs from recorded audio files
2. Display a history of recognized songs

It may look simple at first, but it will showcase a couple of important concepts when applying Clean Architecture to Android development. Find the source code for it [here](https://github.com/davidtakac/horton).

Why clean?


There are a couple of ways to recognize songs embedded into audio files. The most popular one uses a database that associates a song’s sonic fingerprint with its metadata (title, artist, cover art, etc.). This fingerprint encodes the sonic characteristics of a song, which means it's flexible enough to be matched against audio files **similar** to it, but not exactly like it. This makes it perfect for recognizing recorded audio as it always contains background noises such as car hum or nearby conversations.

Some providers of these services, like Shazam, perform fingerprint generation on-device. It's a very privacy-friendly feature since you can't recreate it into the original sound. Others, like Audd, require sending the audio file to their servers. But they all have one thing in common – they take audio as input, perform some magic, and deliver a recognized song. It's the app’s first and foremost use case.

How the app performs this magic shouldn’t be set in stone. Since this isn’t a first-party app where we cooperate with a dedicated backend team to deliver a mobile solution, any of the chosen providers can decide to change their API, which would leave us stranded. This is why we need to separate the app’s business rules from the way it gets data. And that's exactly where Clean architecture steps in.

### Modules
In his book, Uncle Bob talks about the concept of "depending in the direction of stability". This means that each part of your app’s code should depend on code that’s more stable than itself. Stable code is less likely to change than some other code. The two requirements I mentioned earlier should be the most stable parts of the entire app.

The well-known thing to every Android developer is that the tools for implementing these requirements, such as network and database communication or the Android framework, change frequently. When that happens, the rest of the app’s code needs to adapt. Of course, changing more code than you need to is never a good thing. To mitigate this, you should abstract the tools behind a more stable interface.

Java interfaces and abstract classes in a single-module project are great for this task. However, there's one downside – a new developer could accidentally bypass the agreed-upon architectural boundaries, since all project files are visible to him. Luckily, Gradle modules prevent this by denying a developer access to its code and forcing him to respect the architectural boundaries. We can divide this project into four Gradle modules, which approximate Uncle Bob’s Clean architecture, with some simplifications.

```
Modules
   - app
   - auddrecognizer
   - domain
   - roompersistence
```

The domain module is the most important one. It encapsulates the entity and the use case layers from the book, which means it contains the app’s business rules. The entity layer could have been a separate module, but it would only contain a single data class with no methods. To simplify, I merged it into the domain module. But for more complex projects with multiple entities, those modules can be separated. It’s also a pure Kotlin module independent of the Android framework, which gives it two very attractive properties:

1. Testability. You can test pure Kotlin code independently of the Android framework, which reduces complexity.
2. Reusability. You can reuse the domain module and its business rules on any platforms that support Kotlin.

The domain module doesn't depend on any other module. Instead, all other modules depend on it. This is what makes it the most stable component and a natural candidate for the project's backbone. It has no outside reason to change, and the fact that all other modules depend on it discourages change even further.

### The domain module
In Clean architecture, use cases are representations of an app’s requirements in code form. They take input and transform it into output in a way specific to the app. They are in the domain module and are the only concrete implementations inside it. The rest are interfaces implemented by the other modules to provide concrete behavior.

```kotlin
class RecognizeSongUseCase(
  private val songRecognizer: SongRecognizer,
  private val songSaver: SongSaver
) {
  val sampleDuration: Duration = songRecognizer.sampleDuration

  suspend fun recognize(songFilePath: String): RecognizeSongResult {
      val result = songRecognizer.recognize(songFilePath)
      if (result is RecognizeSongResult.Success) {
          try {
              songSaver.save(result.recognizedSong)
          } catch (e: Exception) {
              // Ignore this because saving to history is not critical
          }
      }
      return result
  }
}
```

Here I implemented the use case’s input port with the method’s input parameters and the output port with the `suspend` keyword. The key purpose of an output port is that the use case can defer the delivery of its result. In other words, it needs to support async operations, because its calculations could block. In Java, you would implement this with callbacks. In Kotlin, you can replace them with coroutines.

You can see a code snippet of the app's central use case above – the `RecognizeSongUseCase`. It has a simple public interface. The `sampleDuration` variable lets the consumer know how long the sample sound should be to achieve the best recognition, as this is expected to vary between services.

It has two dependencies. `SongRecognizer` is the interface responsible for the actual song recognition. Depending on the implementation, you can use the audio file to generate a fingerprint which is then sent to an API that returns a recognition. You can also implement it to perform on-device recognition with an offline database. The important thing is that the use case doesn’t know about the implementation – that's the responsibility of recognizer modules.

`SongSaver`, its second dependency, is an interface responsible for saving successfully recognized songs for later access. Again, the use case doesn't know how it's done. As far as it's concerned, it can be in-memory storage, file storage, or database storage. It only knows that calling it saves songs *somewhere*.

```
└── domain
   ├── entities
   │   └── RecognizedSong.kt
   ├── persistence
   │   ├── SongGetter.kt
   │   └── SongSaver.kt
   ├── recognizer
   │   └── SongRecognizer.kt
   └── usecases
       ├── HistoryUseCase.kt
       └── recognizesong
           ├── RecognizeSongResult.kt
           └── RecognizeSongUseCase.kt
```

This is the domain module's directory structure. The `entities` package contains our app's sole entity. The `persistence` and `recognizer` packages contain interfaces that will be implemented by other modules. Finally, the `usecases` package contains implementations of our app's use cases.

### The recognizer and persistence modules
For the domain module to work, it needs to be injected with concrete implementations of its interfaces. The auddrecognizer module provides `AuddSongRecognizer`, and the persistence module provides `SongRepository`, which implements `SongSaver` and `SongGetter`.

```kotlin
class AuddSongRecognizer(
  private val apiInterface: AuddApiInterface
) : SongRecognizer {
  override val sampleDuration: Duration = Duration.ofSeconds(5L)

  override suspend fun recognize(songFilePath: String): RecognizeSongResult {
      val response = try {
          apiInterface.recognize(buildAuddRequestBody(songFilePath))
      } catch (e: Exception) {
          return RecognizeSongResult.NetworkError(e)
      }
      return if (response.result == null) {
          RecognizeSongResult.NotRecognized
      } else {
          RecognizeSongResult.Success(response.result.mapToEntity())
      }
  }

  private fun buildAuddRequestBody(songFilePath: String): RequestBody {...}
}
```

In the code snippet above, you can see that `AuddSongRecognizer` provides a sample duration of 5 seconds, determined by trial and error. Audd doesn’t recommend a particular sample duration. It also provides an implementation of the `recognize` method, which builds an OkHttp request body with the input audio file and sends it to their servers for recognition using a Retrofit API interface. Then it parses the received response and, if successful, converts the response object to our entity and returns it. Our usage of Audd for song recognition is completely abstracted away. You can replace it with relative ease by providing an alternative implementation of `SongRecognizer`.

```kotlin
class SongRepository(
  private val songDao: SongDao
) : SongSaver, SongGetter {
  override suspend fun getAll(): List<RecognizedSong> {
      return songDao
          .getAll()
          .map { row -> row.mapToEntity() }
  }

  override suspend fun save(recognizedSong: RecognizedSong) {
      songDao.save(recognizedSong.mapToRow())
  }
}
```

Next, you can see that `SongRepository` just delegates its functionalities to a Room data access object (DAO). When getting all saved songs, it maps them from database rows to entities. When saving an entity, it needs to convert it to an object the database understands, so it converts it to a database row. The important thing to note here is that **the database is merely a way to persist entities**. As such, it's an implementation detail and should be abstracted away, as we did here.

A more complex app would likely have more modules like these that provide implementations of abstract domain behaviors. For example, if this app supported playback of recorded audio snippets, the module could have another use case called `PlaySnippetUseCase` and an interface called `Player`. It would be implemented in an exoplayer module which would use Android’s ExoPlayer library to play the provided audio file. Just like the previous two modules, the domain module would only know of the `Player` interface and not that it’s implemented with ExoPlayer. Then if you wish to replace it with something else, you can easily and safely do so.

All good for now? Hope you're keeping up. As we still have a lot of work, let’s take a break here. To learn how to tie the modules together and display the use case results, read part 2.

Like what you just read?

Feel free to spread the news!

About the author

David is an Android Developer at COBE, who's passionate about Clean Architecture, Kotlin Multiplatform Mobile, and Linux. When he's not working, he's probably out in nature – riding his bike, or doing some calisthenics.

David

Android Developer

Write
David
Write COBE
Related Articles

Still interested? Take a look at the following articles.