The UseCase Lie: What Android Developers Need to Know
Introduction
If you’ve been in the Android development world for a while, you’ve probably heard of UseCases. They are often presented as the holy grail of “Clean Architecture.” UseCases promise to separate business logic from presentation and data layers, making your code more modular, reusable, and testable. But here’s the catch: UseCases are not always the answer.
In fact, blindly applying them can lead to bloated code and unnecessary complexity, which is exactly what Clean Architecture tries to avoid. In this article, we’re going to break down the myth surrounding UseCases and discuss when they are essential — and when they’re just a waste of time. If you’re an Android developer wondering whether you’re doing more harm than good by following this pattern, this article is for you.
The Controversial Truth About UseCases
In theory, UseCases make perfect sense in a clean architecture setup: they encapsulate business logic and ensure that your app’s layers remain decoupled. However, the reality in day-to-day app development is much more nuanced.
Too many developers treat UseCases as a requirement, leading to redundant and unnecessary code. The result? Instead of making their apps more maintainable and scalable, they’re creating bloated codebases filled with layers of abstraction that don’t serve a real purpose.
When You Should Avoid UseCases: Keep It Simple
Let’s start with when UseCases should be avoided. The biggest mistake many developers make is using UseCases for every small action in the app, even when no significant business logic is involved.
Example: Fetching Data from a Repository
Imagine you’re building a simple to-do app where you need to fetch a list of tasks from a local database. The action is straightforward: you query the repository, get the list, and display it in the UI.
Using a UseCase here would only add unnecessary complexity. You don’t have any complex business logic that needs to be isolated or reused across different parts of the app. The ViewModel can directly call the repository without involving a UseCase.
class TaskViewModel(private val repository: TaskRepository) : ViewModel() {
val tasks = liveData {
val taskList = repository.getTasks()
emit(taskList)
}
}
Why avoid a UseCase here? Because it doesn’t add value. The logic is already simple, and introducing a UseCase would only bloat the code without any clear benefit. Clean Architecture advocates for simplicity and clarity, and here, a UseCase would do the opposite.
When a UseCase is Necessary: Handling Complex Business Logic
Now, let’s talk about when a UseCase is actually necessary. UseCases shine when you need to encapsulate complex business logic that needs to be decoupled from the UI and data layers.
Example: Booking a Flight in a Travel App
Consider a scenario where a user is booking a flight in a travel app. This process involves multiple steps:
- Validating user input (dates, destinations).
- Checking flight availability.
- Reserving the flight.
- Processing payment.
- Sending a booking confirmation.
Here, you’re dealing with multiple interactions between different services — flight availability, reservation, and payment — each of which can fail or require specific error handling.
A UseCase in this scenario makes perfect sense. By encapsulating the booking process in a UseCase, you can:
- Ensure all business rules are followed.
- Reuse the logic across different parts of the app (e.g., booking through different UIs).
- Make it easier to test the booking logic independently of the UI and data layers.
class BookFlightUseCase(
private val flightRepository: FlightRepository,
private val paymentProcessor: PaymentProcessor
) {
suspend operator fun invoke(flightId: String, userDetails: User, paymentInfo: PaymentInfo): BookingResult {
if (!validateInput(userDetails)) throw InvalidInputException()
val availability = flightRepository.checkAvailability(flightId)
if (!availability) throw FlightNotAvailableException()
val reservation = flightRepository.reserveFlight(flightId, userDetails)
val paymentResult = paymentProcessor.processPayment(paymentInfo)
return BookingResult.Success(reservation, paymentResult)
}
}
In this case, the UseCase improves code organization, testability, and reusability. Without a UseCase, this logic would likely end up in the ViewModel or the Activity, making it harder to maintain, test, and reuse.
The Real Purpose of UseCases: Avoiding Bloat, Not Creating It
The goal of Clean Architecture is not to create more layers of abstraction for the sake of it, but to keep your codebase clean, organized, and easy to maintain. Introducing a UseCase where it isn’t necessary violates this principle.
Here’s where many developers go wrong: they follow patterns like UseCases because they’re told it’s part of Clean Architecture without fully understanding why they’re using it. As a result, their code becomes cluttered with UseCases that don’t serve any real purpose, turning Clean Architecture into the very thing it’s supposed to prevent — bloated and hard-to-maintain code.
Conclusion: UseCases are Tools, Not Rules
The key takeaway is this: UseCases are tools, not rules. Just because Clean Architecture suggests them doesn’t mean they should be applied everywhere. Overuse of UseCases, especially in situations with no complex business logic, leads to unnecessary abstraction and bloated code.
When deciding whether or not to implement a UseCase, ask yourself:
- Is there business logic that needs to be separated from the presentation layer?
- Will this logic be reused elsewhere in the app?
- Does this logic need to be independently tested?
If the answer to these questions is no, skip the UseCase and keep your code simple. If the answer is yes, then a UseCase will help you achieve a cleaner, more maintainable codebase.
Use UseCases wisely, and you’ll be on the right path to writing clean, efficient Android code.
Closure:
If this article helped you see UseCases in a new light, or if it gave you the clarity you need to make better architectural decisions in your Android projects, I’d greatly appreciate it if you gave it a clap. Your support helps spread the word and adds value to the community.
Feel free to connect with me on LinkedIn for more insights, discussions, and updates on Android development and best practices.
Thank you for reading, and happy coding!