Clean Architecture in Android — Complete Guide
1. What is Clean Architecture?
Clean Architecture is a design pattern proposed by Robert C. Martin (Uncle Bob) that emphasizes **separation of concerns**, **testability**, and **maintainability**. It divides the codebase into layers where dependencies always point **inward**, ensuring that core business logic is independent of frameworks, UI, or external systems.
2. Core Layers of Clean Architecture
- Entities: Encapsulate the core business rules. They are independent of frameworks and external dependencies.
- Use Cases / Interactors: Contain application-specific business rules. They orchestrate the flow of data to and from the Entities.
- Interface Adapters / Presenters: Convert data from the use cases into a form suitable for the UI, database, or external services.
- Frameworks & Drivers: Outer layer containing Android SDK, databases, network libraries, and UI components (Compose or XML).
3. Key Principles
- Dependency Rule: Source code dependencies always point inward, never outward.
- Separation of Concerns: Each layer has a single responsibility and does not rely on outer layers.
- Testability: Core business logic can be tested independently of the Android framework.
4. Advantages of Clean Architecture
- Highly maintainable and scalable for large apps.
- Easy to test core business logic.
- Allows swapping frameworks or UI without affecting business rules.
- Decouples UI, data, and business logic effectively.
5. Example: Simple Login Feature Using Clean Architecture
📁 Folder Structure
├── domain/
│ ├── model/User.kt
│ └── usecase/LoginUseCase.kt
├── data/
│ ├── repository/UserRepositoryImpl.kt
│ └── source/UserDataSource.kt
├── presentation/
│ ├── view/LoginActivity.kt
│ └── viewmodel/LoginViewModel.kt
└── di/
└── AppModule.kt
🧩 Domain Layer — Entities & Use Case
// User Entity
package com.example.cleanarchitecture.domain.model
data class User(val username: String, val password: String) {
fun isValid(): Boolean = username.isNotEmpty() && password.length >= 6
}
// Login Use Case
package com.example.cleanarchitecture.domain.usecase
import com.example.cleanarchitecture.domain.model.User
import com.example.cleanarchitecture.domain.repository.UserRepository
class LoginUseCase(private val repository: UserRepository) {
fun execute(username: String, password: String): Boolean {
val user = User(username, password)
return if (user.isValid()) repository.login(user) else false
}
}
📦 Data Layer — Repository & Data Source
package com.example.cleanarchitecture.data.repository
import com.example.cleanarchitecture.domain.model.User
import com.example.cleanarchitecture.domain.repository.UserRepository
import com.example.cleanarchitecture.data.source.UserDataSource
class UserRepositoryImpl(private val dataSource: UserDataSource) : UserRepository {
override fun login(user: User): Boolean {
return dataSource.login(user)
}
}
// Simulated Data Source
package com.example.cleanarchitecture.data.source
import com.example.cleanarchitecture.domain.model.User
class UserDataSource {
fun login(user: User): Boolean {
// Simulate authentication
return user.username == "admin" && user.password == "123456"
}
}
🖥️ Presentation Layer — ViewModel & Activity
package com.example.cleanarchitecture.presentation.viewmodel
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import com.example.cleanarchitecture.domain.usecase.LoginUseCase
class LoginViewModel(private val loginUseCase: LoginUseCase) : ViewModel() {
private val _loginResult = MutableLiveData()
val loginResult: LiveData get() = _loginResult
fun login(username: String, password: String) {
val success = loginUseCase.execute(username, password)
_loginResult.value = if (success) "Login Successful" else "Invalid Credentials"
}
}
// Activity (XML or Compose)
package com.example.cleanarchitecture.presentation.view
import android.os.Bundle
import android.widget.Toast
import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity
import com.example.cleanarchitecture.presentation.viewmodel.LoginViewModel
class LoginActivity : AppCompatActivity() {
private val viewModel: LoginViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// setContentView(R.layout.activity_login)
viewModel.loginResult.observe(this) { message ->
Toast.makeText(this, message, Toast.LENGTH_SHORT).show()
}
// On button click:
// viewModel.login(username, password)
}
}
6. Summary
- Clean Architecture separates code into layers: Domain → Data → Presentation → Framework.
- Dependencies always point inward, making the core business logic independent of Android frameworks.
- Highly maintainable, testable, and scalable for large projects.
- Works perfectly with MVVM or MVI in the Presentation layer.
Conclusion
Clean Architecture is the gold standard for professional Android applications. It ensures a clean separation between business logic, data, and UI, allowing for easier testing, maintenance, and future-proofing of your app. This architecture is highly favored in CTO-level interviews.