Looking at many blog posts and GitHub projects, I've noticed that when implementing Android project with Clean Architecture, the Presentation Layer often has dependencies on the Data Layer. I'm not sure if the Presentation Layer really needs to have direct dependencies on the Data Layer. Is this dependency direction correct? If it is, could you provide some examples?
In Clean Architecture, the Presentation Layer should not have direct dependencies on the Data Layer. The main idea of Clean Architecture is to enforce separation of concerns and maintain a clear flow of dependencies. This is done by ensuring that the core business logic and data fetching are abstracted away from the user interface (UI).
Dependency Flow in Clean Architecture:
The correct flow should be: Presentation → Domain → Data: The Presentation Layer communicates with the Domain Layer, and the Domain Layer communicates with the Data Layer. However, the Data Layer should not know about the Presentation Layer at all.
A common mistake is when developers inject or directly reference data sources (like Retrofit services or Room DAOs) in the Presentation Layer (e.g., ViewModel or Activity). This breaks the architecture's intended isolation between the UI and the data sources.
A simple example of Clean architecture flow is as below
Domain Layer (Use Cases) Defines business logic and abstracts away how data is fetched or saved. It interacts with repositories.
interface UserRepository {
suspend fun getUserDetails(userId: String): User
}
class GetUserDetailsUseCase(private val userRepository: UserRepository {
suspend fun execute(userId: String): User {
return userRepository.getUserDetails(userId)
}
}
Data Layer (Repositories and Data Sources) Data layer implements the repository interface defined in the domain layer. It handles the details of fetching data from an API or database.
class UserRepositoryImpl(private val userApi: UserApi) : UserRepository {
override suspend fun getUserDetails(userId: String): User {
return userApi.getUserDetails(userId)
}
}
Presentation Layer (ViewModel or Presenter) Presentation Layer interacts with the domain layer (specifically the use cases) and should not directly access the data sources. ViewModels are a common component used in the presentation layer.
class UserViewModel(private val getUserDetailsUseCase: GetUserDetailsUseCase) : ViewModel() {
private val _userDetails = MutableLiveData<User>()
val userDetails: LiveData<User> get() = _userDetails
fun fetchUserDetails(userId: String) {
viewModelScope.launch {
val user = getUserDetailsUseCase.execute(userId)
_userDetails.postValue(user)
}
}
}
Dependency Injection (DI) to Provide Dependencies Injecting dependencies into the Presentation Layer (e.g., ViewModel). The ViewModel will get the UseCase (from the Domain Layer), and the UseCase will get the Repository (from the Data Layer).
@Module
@InstallIn(ViewModelComponent::class)
object AppModule {
@Provides
fun provideUserRepository(userApi: UserApi): UserRepository {
return UserRepositoryImpl(userApi)
}
@Provides
fun provideGetUserDetailsUseCase(userRepository: UserRepository):GetUserDetailsUseCase {
return GetUserDetailsUseCase(userRepository)
}
}