0
0
Android Kotlinmobile~15 mins

Use cases / Interactors in Android Kotlin - Deep Dive

Choose your learning style9 modes available
Overview - Use cases / Interactors
What is it?
Use cases, also called interactors, are parts of an app that handle specific tasks or actions. They focus on doing one job, like fetching data or saving user input, separate from the user interface or data storage. This separation helps keep the app organized and easier to change or test. Think of use cases as the app's action managers that know exactly how to do one thing well.
Why it matters
Without use cases, app logic often gets mixed up with UI or data code, making apps messy and hard to fix or improve. Use cases solve this by keeping each task clear and separate, so developers can work faster and avoid bugs. This makes apps more reliable and easier to update, which means better experiences for users and less stress for developers.
Where it fits
Before learning use cases, you should understand basic app layers like UI and data storage. After mastering use cases, you can learn about advanced app architecture patterns like Clean Architecture or MVVM, which use use cases to organize code better.
Mental Model
Core Idea
Use cases are focused action units that handle one specific job in an app, keeping business logic separate from UI and data layers.
Think of it like...
Use cases are like specialized workers in a factory, each responsible for one task, so the whole factory runs smoothly without confusion.
┌─────────────┐   ┌─────────────┐   ┌─────────────┐
│   UI Layer  │ → │ Use Cases / │ → │ Data Layer  │
│ (User View) │   │ Interactors │   │ (Storage)   │
└─────────────┘   └─────────────┘   └─────────────┘
Build-Up - 7 Steps
1
FoundationWhat Are Use Cases?
🤔
Concept: Use cases represent single, focused tasks in an app's logic.
In an app, many actions happen: loading data, saving info, or processing input. Use cases are small pieces of code that do one of these actions clearly and separately. For example, a use case might be 'GetUserProfile' which only knows how to fetch user data.
Result
You understand that use cases are small, focused parts of app logic that do one job.
Understanding that use cases isolate tasks helps keep app code clean and easier to manage.
2
FoundationWhy Separate Logic from UI?
🤔
Concept: Separating logic from UI makes apps easier to change and test.
If you mix how the app looks with how it works, changing one part can break the other. By putting logic in use cases, the UI just shows data and sends user actions, while use cases handle the work behind the scenes.
Result
You see that separating concerns prevents bugs and speeds up development.
Knowing this separation reduces errors and makes teamwork smoother.
3
IntermediateHow Use Cases Communicate Layers
🤔Before reading on: do you think use cases talk directly to UI or data layers? Commit to your answer.
Concept: Use cases act as middlemen between UI and data layers.
The UI layer asks use cases to perform tasks. Use cases then get or save data through the data layer. This keeps UI unaware of data details and data layer unaware of UI needs.
Result
You understand use cases connect UI and data cleanly without mixing their code.
Recognizing use cases as connectors clarifies app structure and improves maintainability.
4
IntermediateImplementing a Simple Use Case
🤔Before reading on: do you think a use case should know about UI details? Commit to yes or no.
Concept: Use cases focus on business logic, not UI details.
In Kotlin, a use case can be a class with a function like 'execute()' that runs the task. For example: class GetUserProfile(private val repo: UserRepository) { suspend fun execute(userId: String): User { return repo.getUser(userId) } } This code fetches user data without knowing how UI shows it.
Result
You can write a simple use case that performs one task independently.
Knowing use cases avoid UI code keeps responsibilities clear and code reusable.
5
IntermediateTesting Use Cases Separately
🤔Before reading on: do you think testing use cases is easier or harder than testing UI? Commit to your answer.
Concept: Use cases can be tested alone without UI or data layers.
Because use cases focus on logic, you can test them by giving fake data sources and checking results. This means bugs are found faster and fixes are safer.
Result
You see that use cases improve app quality by making testing simpler.
Understanding testability of use cases leads to more reliable apps.
6
AdvancedUse Cases in Clean Architecture
🤔Before reading on: do you think use cases depend on UI or data layers? Commit to your answer.
Concept: In Clean Architecture, use cases form the app's business rules and depend on abstractions, not details.
Use cases sit in the middle layer, depending only on interfaces, not concrete UI or data code. This allows swapping data sources or UI without changing use cases. For example, a use case calls a repository interface, which can have different implementations.
Result
You understand use cases as stable, independent business logic layers.
Knowing this dependency rule prevents tight coupling and eases app evolution.
7
ExpertHandling Complex Use Case Interactions
🤔Before reading on: do you think one use case can call another? Commit to yes or no.
Concept: Use cases can coordinate by calling each other or combining results for complex tasks.
Sometimes a task needs multiple steps, like 'PlaceOrder' which checks stock, processes payment, and updates inventory. This can be done by composing smaller use cases or orchestrating them carefully to keep code clean and testable.
Result
You can design complex app logic by combining simple use cases.
Understanding use case composition helps manage complexity without losing clarity.
Under the Hood
Use cases are plain Kotlin classes or functions that encapsulate business logic. They receive input, interact with data repositories or services through interfaces, and return results. They do not hold UI state or directly manipulate UI components. This separation is enforced by design patterns and dependency inversion, allowing use cases to be independent and testable.
Why designed this way?
Use cases were designed to solve the problem of tangled code where UI, data, and logic mix. By isolating business rules, apps become easier to maintain and evolve. This approach follows principles from Clean Architecture and SOLID design, promoting separation of concerns and dependency inversion to reduce coupling.
┌─────────────┐
│    UI       │
│ (Activity)  │
└─────┬───────┘
      │ calls
┌─────▼───────┐
│ Use Case    │
│ (Interactor)│
└─────┬───────┘
      │ uses
┌─────▼───────┐
│ Repository  │
│ Interface   │
└─────┬───────┘
      │ implemented by
┌─────▼───────┐
│ Data Source │
│ (DB/Network)│
└─────────────┘
Myth Busters - 4 Common Misconceptions
Quick: Do use cases handle UI updates directly? Commit to yes or no.
Common Belief:Use cases update the UI directly since they perform app actions.
Tap to reveal reality
Reality:Use cases do not know about UI; they only handle business logic and return results for the UI to display.
Why it matters:Mixing UI code into use cases makes apps harder to maintain and test, causing bugs and slow development.
Quick: Are use cases the same as data repositories? Commit to yes or no.
Common Belief:Use cases and repositories are the same because both deal with data.
Tap to reveal reality
Reality:Repositories handle data access, while use cases handle business logic and coordinate data from repositories.
Why it matters:Confusing these roles leads to messy code where logic and data access are mixed, reducing clarity.
Quick: Should use cases depend on concrete data or UI classes? Commit to yes or no.
Common Belief:Use cases can depend on concrete UI or data classes for convenience.
Tap to reveal reality
Reality:Use cases should depend only on abstractions (interfaces) to stay independent and testable.
Why it matters:Direct dependencies cause tight coupling, making code fragile and hard to change.
Quick: Can one use case call another use case? Commit to yes or no.
Common Belief:Use cases should never call each other to keep things simple.
Tap to reveal reality
Reality:Use cases can call or compose other use cases to handle complex workflows cleanly.
Why it matters:Avoiding use case composition can lead to duplicated code and harder-to-maintain logic.
Expert Zone
1
Use cases often return results wrapped in types like Kotlin's Result or sealed classes to handle success and failure clearly.
2
Use cases can be designed as suspend functions to support asynchronous operations seamlessly with Kotlin coroutines.
3
In large apps, use cases may be grouped into interactor classes that coordinate multiple related use cases for better organization.
When NOT to use
Use cases are less useful for very simple apps where logic is minimal and separation adds unnecessary complexity. In such cases, direct UI-to-repository calls may suffice. Also, for UI-only logic like animations or view state, use cases are not the right place.
Production Patterns
In production, use cases are injected into ViewModels or Presenters using dependency injection frameworks like Hilt. They are tested with mocked repositories to ensure business rules work correctly. Complex apps use layered Clean Architecture where use cases form the core business logic layer, ensuring app stability and flexibility.
Connections
Clean Architecture
Use cases form the central business logic layer in Clean Architecture.
Understanding use cases clarifies how Clean Architecture separates concerns and manages dependencies.
Command Pattern (Software Design)
Use cases resemble commands that encapsulate a request as an object.
Knowing the command pattern helps understand how use cases package actions for flexible execution.
Factory Workflow Management (Business Process)
Use cases are like specialized workers in a factory workflow, each handling a specific task.
Seeing use cases as workflow steps helps grasp their role in coordinating complex app processes.
Common Pitfalls
#1Mixing UI code inside use cases.
Wrong approach:class GetUserProfile { fun execute() { textView.text = "Loading..." // fetch data } }
Correct approach:class GetUserProfile(private val repo: UserRepository) { suspend fun execute(): User { return repo.getUser() } }
Root cause:Confusing responsibilities leads to tangled code that is hard to test and maintain.
#2Use cases depending on concrete data classes instead of interfaces.
Wrong approach:class GetUserProfile(private val repo: UserRepositoryImpl) { ... }
Correct approach:class GetUserProfile(private val repo: UserRepository) { ... }
Root cause:Tight coupling reduces flexibility and testability.
#3Writing large use cases that do many unrelated tasks.
Wrong approach:class ManageOrder { fun execute() { checkStock() processPayment() updateInventory() sendNotification() } }
Correct approach:class ManageOrder( private val checkStockUseCase: CheckStock, private val paymentUseCase: ProcessPayment, private val updateInventoryUseCase: UpdateInventory ) { suspend fun execute() { checkStockUseCase.execute() paymentUseCase.execute() updateInventoryUseCase.execute() } }
Root cause:Not breaking down tasks leads to complex, hard-to-maintain code.
Key Takeaways
Use cases are focused units of business logic that keep app actions clear and separate from UI and data layers.
Separating logic into use cases improves app maintainability, testability, and flexibility.
Use cases act as middlemen between UI and data, depending only on abstractions to reduce coupling.
Complex app logic can be managed by composing simple use cases, avoiding duplication and confusion.
Understanding use cases is key to mastering modern app architecture patterns like Clean Architecture.