Android Developers
Published in

Android Developers

Adding a domain layer

In this article, I’ll explain how we added a domain layer to the Now in Android app for improved readability, scalability and portability.

In Android app architecture, the domain layer holds business logic — the rules that dictate how the app works. For the remainder of this article, I’ll use the term “logic” to refer to “business logic”, as opposed to “UI logic” or any other form of logic.

It’s common to introduce the domain layer as an app grows in complexity, and use it to encapsulate complex logic or logic that is reused by many screen level state holders, like ViewModels.

The pull request which this article is based on is here. I’ve simplified and renamed some class names to focus on the core concepts.

Use cases

The domain layer is created by moving logic, typically from the UI layer, into use cases. Use cases are functions (or classes with a single public method) which contain logic. They perform a single operation which typically combines or transforms data from repositories or other use cases.

The naming convention for use cases in this article (and in the Now in Android app) follows the official guidance of:

  • verb in present tense e.g. Get
  • noun/what e.g. FollowableTopic
  • UseCase suffix.

Example: GetFollowableTopicUseCase

Process

Here’s an overview of the process we used:

  • Identify duplicate and complex logic inside ViewModels
  • Create appropriately named use cases
  • Move logic inside use cases
  • Refactor ViewModels to depend on use cases instead of repositories
  • Add tests for use cases

Identify duplicate logic and create use cases

The following diagram shows the data which is observed by each ViewModel. Each box in the Observes column represents logic, typically combining data from multiple streams. Each box represents a candidate for a use case, and those with the same color indicate duplicate logic.

The “UseCase” suffix is omitted on the diagram for readability.

Move logic inside use cases

Now that the hard part of “naming things” is done, we just need to move the logic from each ViewModel into its relevant UseCase. Let’s take a look at an example.

Logic to observe news articles is used in three different ViewModels.

Let’s take a look at this logic inside BookmarksViewModel:

Here’s what’s going on:

  1. Bookmarks are obtained from UserDataRepository
  2. News resources (aka articles) are obtained from NewsRepository
  3. These two are combined to create a list of bookmarked news resources
  4. The list is filtered to only show bookmarked news resources

The logic for steps 1–3 is common to all other ViewModels, so can be moved into a new use case named GetSaveableNewsResourcesUseCase. Here’s the code:

Domain Layer module and models

Use cases live in the domain layer, and to clearly separate this layer from the rest of our codebase we created a :domain module. Classes created by use cases, usually due to combining data models from more than one repository, were also moved into the :domain module.

A good example of this is the SaveableNewsResource data class which is a result of combining a NewsResource supplied from the NewsRepository and its isSaved property which is calculated using the list of bookmarks from the UserDataRepository.

Update ViewModels to depend on use cases

Now we’ve created GetSaveableNewsResourcesUseCase we can refactor BookmarksViewModel to call it.

The ViewModel is now simpler, easier to read. It’s clear from the constructor what this class is doing — getting saveable news resources, and there’s no need for an intermediate bookmarks variable to store data from the user data repository.

There’s still some logic to filter out the non-bookmarked news articles. We could have moved this filtering logic to another use case (perhaps named GetSavedNewsResourcesUseCase) but creating a whole new class for one call to filter probably doesn’t justify the increased complexity of an extra use case. Ultimately, it’s up to you to decide how much logic to move into your use cases.

The other ViewModels can now be refactored to depend on our use case. Adding in any common logic, such as the ability to return news resources for a given topic or author, as we go.

Summary

We repeated this process for each area of duplicate logic:

  • pick a ViewModel
  • move the logic into a use case
  • refactor the other ViewModels to use the use case

Of course, we also added tests for every use cas, and because use cases are simple functions they’re very easy to test! Ultimately, by adding a domain layer, it’s made our codebase easier to maintain and scale.

You can read more about the domain layer in Now in Android in the Architecture learning journey, and more in depth guidance on the official Android developer website.

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store