Clean Architecture & Use Cases Quiz
ARCHITECTURE › Patterns
According to the dependency rule, which dependency direction is allowed?
- The domain layer depends on the UI layer so it can update screens
- The UI layer depends on the domain layer, which depends on data
- The data layer depends on the domain layer to learn the use cases
- Every layer depends on every other layer for maximum flexibility
Answer: The UI layer depends on the domain layer, which depends on data
Dependencies point inward: UI depends on domain, domain depends on data. Inner layers (domain, data) never depend on the outer UI layer.
Which naming follows Android's convention for a use case?
- NewsManager for handling and coordinating all news data
- AuthorsRepositoryImpl for fetching and storing authors
- GetLatestNewsWithAuthorsUseCase: verb + noun + UseCase
- NewsUseCaseHelper for supporting shared use case logic
Answer: GetLatestNewsWithAuthorsUseCase: verb + noun + UseCase
The convention is verb in present tense + noun + 'UseCase'. GetLatestNewsWithAuthorsUseCase matches; the others are a manager, a repository, and a generic helper.
Why should the domain layer avoid Android framework dependencies?
- Because Gradle cannot compile Android code inside a domain module
- So the business logic stays pure Kotlin, reusable and testable
- Because the Android SDK is not allowed inside plain Kotlin files
- So the domain layer can use Context without causing memory leaks
Answer: So the business logic stays pure Kotlin, reusable and testable
Keeping the domain layer pure Kotlin makes it reusable (Wear, TV, KMP) and testable on the JVM with plain unit tests, independent of the volatile Android SDK.
When is introducing a use case LEAST justified?
- When the same logic is reused across several different ViewModels
- When the operation must combine data from multiple repositories at once
- When it is a thin one-to-one pass-through to one repository method
- When the operation encodes complex, multi-step business rules
Answer: When it is a thin one-to-one pass-through to one repository method
Android's guidance warns that wrapping a simple single-repository call in a use case adds complexity for little benefit. Reuse, combining repositories, and complex rules are the cases that justify one.
What does it mean that domain-layer use cases must be 'main-safe'?
- They must run on a dispatcher that is dedicated to the main thread.
- Safe to call on the main thread; it moves blocking work off it.
- They must never be invoked from a ViewModel or its coroutine scope.
- They must be annotated with @MainThread so the code will compile.
Answer: Safe to call on the main thread; it moves blocking work off it.
Main-safe means a caller can invoke the use case on the main thread; the use case itself shifts any blocking work to a background dispatcher, e.g. via withContext.
How are use cases commonly made callable like a function in Kotlin?
- By overriding the run() method from Runnable
- By defining operator fun invoke()
- By extending an abstract UseCase base class with execute()
- By marking the class with the @Invokable annotation
Answer: By defining operator fun invoke()
Defining operator fun invoke() lets you call an instance like a function, e.g. getLatestNews(). No base class or annotation is required.
How does the official guidance classify the domain layer?
- Optional; recommended in big apps to reuse logic or ease ViewModel load
- Strongly recommended for every app, even the most trivial single-screen ones
- Deprecated now in favor of putting all app logic inside the ViewModel
- Mandatory in every app that uses Jetpack Compose for rendering UI
Answer: Optional; recommended in big apps to reuse logic or ease ViewModel load
The UI and data layers are strongly recommended, while the domain layer is optional and recommended in big apps when you need to reuse data-layer logic or simplify a complex ViewModel.
How does Clean Architecture apply dependency inversion to a repository the domain needs?
- The repository interface lives in the domain, and data supplies the impl.
- Both the interface and its implementation live in data, and the domain uses the class directly.
- The interface is declared in the UI layer, so ViewModels choose an implementation at runtime.
- No interface is used; the use case creates the concrete repository on its own.
Answer: The repository interface lives in the domain, and data supplies the impl.
Dependency inversion puts the repository abstraction (interface) in the domain and the concrete implementation in the data layer, so the inner domain depends on an interface it owns rather than on outer framework details.
Can a use case depend on another use case?
- No, a use case may never call another use case under any condition
- Only if the other use case is moved into the data layer first
- Yes, a use case can depend on other use cases and on repositories
- Only through Java reflection so the dependency stays hidden
Answer: Yes, a use case can depend on other use cases and on repositories
Android's guidance explicitly allows use cases to depend on other use cases and on repositories, letting you compose higher-level operations from smaller ones.
Why do many apps define separate domain models instead of passing network DTOs and database entities straight through?
- Because Kotlin forbids reusing a data class across more than one module or layer
- Because Retrofit cannot deserialize JSON directly into a domain model class object
- Because Room requires every entity to implement Parcelable before it can be stored
- So each layer can evolve independently without coupling domain to API or DB shapes
Answer: So each layer can evolve independently without coupling domain to API or DB shapes
Distinct per-layer models with mapping between them keep the domain from being coupled to volatile network or persistence formats, so a schema change in one layer doesn't ripple through the others.
Why are use cases designed to be stateless and created fresh each time they are injected?
- Because Hilt is incapable of injecting any singleton-scoped class
- They own no lifecycle, and mutable state would make sharing one unsafe
- Because the garbage collector cannot reclaim a shared use case instance
- Because a stateful class may not define an operator fun invoke()
Answer: They own no lifecycle, and mutable state would make sharing one unsafe
Use cases have no lifecycle and should hold no mutable data; creating a new instance per injection keeps them safe to reuse across callers without shared-state bugs.
Besides use cases, what else legitimately belongs in the domain layer?
- Activity and Fragment subclasses for the app's screens
- Room DAOs and Retrofit service interfaces in data layer
- Domain models (entities) and repository interfaces
- Jetpack Compose @Composable functions for UI screens
Answer: Domain models (entities) and repository interfaces
The domain layer holds business entities and the repository abstractions use cases depend on; Activities, DAOs, Retrofit services, and Composables all belong to the outer UI or data layers.
Which of the following would violate the design of a domain-layer use case?
- Holding a reference to an Android Context or View
- Exposing a single business operation through operator fun invoke()
- Depending on a repository interface rather than a concrete class
- Being main-safe by shifting blocking work off the main thread with withContext
Answer: Holding a reference to an Android Context or View
Referencing Android framework types like Context or View couples the domain to the SDK, breaking its purity; the other three are exactly how a well-formed use case is meant to behave.
In the recommended architecture, which component typically depends on use cases and exposes state to the UI?
- The repository in the data layer
- The Composable function, which calls use cases directly
- The local data source, such as a Room database
- The ViewModel in the UI layer
Answer: The ViewModel in the UI layer
ViewModels sit in the UI layer, call use cases (or repositories) to run business logic, and expose the resulting observable state; repositories and data sources sit below and Composables consume the ViewModel's state rather than invoking use cases themselves.