Skip to main content
This guide will help you clone, build, and run the Real Clean Architecture in Android sample project. You’ll learn how to navigate the codebase and understand its structure.

Prerequisites

Before you begin, ensure you have the following installed:

Android Studio

Latest stable version with Android SDK

JDK 17

Required for Kotlin compilation

Git

For cloning the repository

Gradle

Included via Gradle Wrapper
The project uses Gradle Wrapper, so you don’t need to install Gradle separately. The wrapper scripts (gradlew and gradlew.bat) handle everything for you.

Clone the Repository

1

Clone the project

Open your terminal and clone the repository:
git clone https://github.com/DenisBronx/Real-Clean-Architecture-In-Android---Sample.git
cd Real-Clean-Architecture-In-Android---Sample
2

Open in Android Studio

Launch Android Studio and select File > Open, then navigate to the cloned directory.
Android Studio will automatically detect the Gradle project and begin syncing dependencies. This may take a few minutes on first run.

Build the Project

Using Android Studio

Once the Gradle sync completes, you can build the project:
  1. Select Build > Make Project from the menu
  2. Wait for the build to complete (watch the Build output at the bottom)

Using Command Line

You can also build from the terminal:
# On macOS/Linux
./gradlew build

# On Windows
gradlew.bat build
For faster builds during development, use ./gradlew assembleDebug instead of build. This skips running tests and only assembles the debug APK.

Run the Application

1

Configure a device

You can run the app on either:
  • Physical device: Enable USB debugging in Developer Options
  • Emulator: Create an AVD (Android Virtual Device) in Android Studio
Minimum SDK: API 24 (Android 7.0)
Target SDK: API 36
2

Run the app

Click the Run button (green play icon) in Android Studio, or use:
./gradlew installDebug
The app will launch and display the login screen.

Project Structure

The project follows a multi-module architecture using “Package by Component” strategy. Here’s the complete module breakdown:

Module Types

General-purpose utilities that can be reused across the project:
  • cache: Key-value storage functionality (SharedPreferences wrapper)
  • cache-test: Testing utilities for the cache module
  • coroutines-test-dispatcher: JUnit rules for coroutine testing
  • designsystem: Reusable UI components and theme
  • flow-test-observer: Utilities for testing Flow emissions
  • foundations: Language utilities not provided by Kotlin (e.g., Answer type)
  • httpclient: Network request handling
  • viewmodel: ViewModel utilities without tight framework coupling
Pure Kotlin/KMP modules containing domain and data layers for specific features:
  • cart-component: Shopping cart domain and data layers
  • money-component: Money domain object shared across modules
  • product-component: Product domain and data logic
  • user-component: User authentication and data management
  • wishlist-component: Wishlist domain and data layers
Component modules are implemented in Kotlin Multiplatform and have no Android framework dependencies. This makes them compile faster and potentially reusable across platforms.
Android-specific presentation layer modules:
  • cart-ui: Cart screen UI components
  • main-ui: Main screen with bottom navigation
  • money-ui: Reusable price display components
  • onboarding-ui: Login screen (and eventually account creation)
  • plp-ui: Product List Page UI
  • wishlist-ui: Wishlist screen components
Central orchestration module that:
  • Integrates all feature modules
  • Contains navigation logic (see app/src/main/java/com/denisbrandi/androidrealca/navigation/RootNavigation.kt:1)
  • Manages dependency injection via CompositionRoot (see app/src/main/java/com/denisbrandi/androidrealca/di/CompositionRoot.kt:1)
  • Defines the main MainActivity (see app/src/main/java/com/denisbrandi/androidrealca/MainActivity.kt:1)

Directory Structure

Real-Clean-Architecture-In-Android---Sample/
├── app/                          # Main application module
│   ├── src/main/
│   │   ├── AndroidManifest.xml
│   │   └── java/com/denisbrandi/androidrealca/
│   │       ├── MainActivity.kt
│   │       ├── di/
│   │       │   └── CompositionRoot.kt
│   │       └── navigation/
│   │           └── RootNavigation.kt
│   └── build.gradle.kts
├── user-component/               # Example component module
│   ├── src/
│   │   ├── commonMain/kotlin/   # Multiplatform source
│   │   │   └── .../user/
│   │   │       ├── domain/      # Business logic
│   │   │       ├── data/        # Data layer
│   │   │       └── di/          # Dependency injection
│   │   └── commonTest/kotlin/   # Unit tests
│   └── build.gradle.kts
├── onboarding-ui/                # Example UI module
│   ├── src/main/java/.../onboarding/
│   │   └── presentation/
│   │       ├── view/            # Composable screens
│   │       └── viewmodel/       # ViewModels
│   └── build.gradle.kts
├── designsystem/                 # Shared UI components
├── foundations/                  # Core utilities
├── build.gradle.kts             # Root build configuration
├── settings.gradle.kts          # Module declarations
└── gradle.properties            # Project properties
While this project uses a multi-module approach for illustration, such extensive modularization may be unnecessary for smaller projects. Consider your project’s size and team structure before adopting this pattern.

Understanding the Architecture

The project strictly adheres to Clean Architecture principles:
1

Domain Layer (Business Logic)

Located in component modules under src/commonMain/kotlin/.../domain/Example: User login use case
// user-component/src/commonMain/kotlin/.../domain/usecase/LoginUseCase.kt
internal class LoginUseCase(
    private val userRepository: UserRepository
) : Login {
    override suspend fun invoke(loginRequest: LoginRequest): Answer<Unit, LoginError> {
        return when {
            !Email(loginRequest.email).isValid() -> {
                Answer.Error(LoginError.InvalidEmail)
            }
            !Password(loginRequest.password).isValid() -> {
                Answer.Error(LoginError.InvalidPassword)
            }
            else -> {
                return userRepository.login(loginRequest)
            }
        }
    }
}
The Answer type from the foundations module is used for error handling instead of exceptions.
2

Data Layer (Repositories)

Located in component modules under src/commonMain/kotlin/.../data/Contains repository implementations and data models (DTOs).
3

Presentation Layer (UI)

Located in UI modules under src/main/java/.../presentation/Example: Login ViewModel
// onboarding-ui/src/main/java/.../viewmodel/RealLoginViewModel.kt
internal class RealLoginViewModel(
    private val login: Login,
    private val stateDelegate: StateDelegate<LoginScreenState>,
    private val eventDelegate: EventDelegate<LoginScreenEvent>
) : LoginViewModel,
    StateViewModel<LoginScreenState> by stateDelegate,
    EventViewModel<LoginScreenEvent> by eventDelegate,
    ViewModel() {

    override fun login(email: String, password: String) {
        stateDelegate.updateState { it.copy(displayState = DisplayState.LoggingIn) }

        viewModelScope.launch {
            login(LoginRequest(email, password)).fold(
                success = {
                    eventDelegate.sendEvent(viewModelScope, LoginScreenEvent.SuccessfulLogin)
                },
                error = { loginError ->
                    stateDelegate.updateState { it.copy(displayState = DisplayState.Form) }
                    eventDelegate.sendEvent(viewModelScope, LoginScreenEvent.ShowError(loginError))
                }
            )
        }
    }
}
4

Dependency Injection

The project uses manual DI through assembler classes:
  • Each module has an assembler in its di/ package
  • The app module’s CompositionRoot orchestrates all assemblers
  • Dependencies are lazily initialized
See app/src/main/java/com/denisbrandi/androidrealca/di/CompositionRoot.kt:1 for the complete setup.

Key Files to Explore

MainActivity.kt

App entry point
app/src/main/java/com/denisbrandi/androidrealca/MainActivity.kt:9

RootNavigation.kt

Navigation graph
app/src/main/java/com/denisbrandi/androidrealca/navigation/RootNavigation.kt:19

CompositionRoot.kt

DI container
app/src/main/java/com/denisbrandi/androidrealca/di/CompositionRoot.kt:13

Answer.kt

Error handling type
foundations/src/commonMain/kotlin/.../Answer.kt:3

Running Tests

The project includes comprehensive unit tests for all layers:
# Run all tests
./gradlew test

# Run tests for a specific module
./gradlew :user-component:test

# Generate code coverage report
./gradlew koverHtmlReport
Code coverage reports are generated in build/reports/kover/html/index.html after running koverHtmlReport.

Test Structure

Tests are located alongside source code:
  • Component modules: src/commonTest/kotlin/
  • UI modules: src/test/java/
Example test locations:
  • user-component/src/commonTest/kotlin/.../domain/usecase/
  • onboarding-ui/src/test/java/.../viewmodel/

Common Gradle Tasks

Here are the most frequently used Gradle commands:
# Build debug APK
./gradlew assembleDebug

# Build release APK
./gradlew assembleRelease

# Clean build artifacts
./gradlew clean

# Install debug APK on connected device
./gradlew installDebug

# Run all tests
./gradlew test

# Generate code coverage
./gradlew koverHtmlReport

# List all modules
./gradlew projects

# Check for dependency updates (if plugin is added)
./gradlew dependencyUpdates

Next Steps

Now that you have the project running, explore these topics to deepen your understanding:

Architecture Overview

Learn about Clean Architecture principles

Module Structure

Understand the modularization strategy

Domain Layer

Explore business logic implementation

Testing Guide

Write and run tests effectively

Troubleshooting

  1. Ensure you have JDK 17 installed
  2. Check your internet connection (Gradle downloads dependencies)
  3. Try invalidating caches: File > Invalidate Caches / Restart
  4. Delete the .gradle directory and sync again
  1. Run ./gradlew clean
  2. Ensure Android SDK is installed with API 36
  3. Check that ANDROID_HOME environment variable is set
  4. Update Android Studio to the latest stable version
  1. Check Logcat in Android Studio for stack traces
  2. Ensure the device/emulator is running at least Android 7.0 (API 24)
  3. Verify the build variant is set to debug
  4. Try cleaning and rebuilding the project
  1. Run ./gradlew clean test
  2. Ensure all dependencies are downloaded
  3. Check if specific test files need updating
  4. Verify JDK 17 is being used for compilation
For more help, check the GitHub repository for issues and discussions.

Build docs developers (and LLMs) love