Do We Really Need Use Cases in Mobile Apps?
Valerii Popov
Posted on October 26, 2024
In mobile development, use cases are often used to encapsulate business logic, providing a clear structure for handling interactions between the app’s presentation layer and data layer. However, in many mobile apps, especially those with straightforward data access needs, use cases end up being simple wrappers around repository calls. Let’s explore what use cases are, why they’re commonly used as repository wrappers, and when you might want to consider omitting them altogether.
1. What is a Use Case?
A use case in software development represents a specific action or task within the application’s business logic. In mobile development, use cases are classes that contain single responsibilities, often designed to perform a single action, like FetchUserProfile
or GetUserPosts
. They serve as intermediaries between the ViewModel (presentation layer) and the repository (data layer), aiming to encapsulate business logic for a single action.
Here’s an example of a simple use case class in Kotlin:
// Example Use Case in Kotlin
class FetchUserProfileUseCase(
private val userRepository: UserRepository
) {
suspend operator fun invoke(userId: String): User {
// The use case orchestrates the fetching of user profile data.
return userRepository.getUserProfile(userId)
}
}
This structure can be beneficial if there’s complex logic involved in obtaining the User data, like validating the userId or applying filters, caching, or error handling. However, if the use case merely calls the repository function, it might add unnecessary layers to the code.
2. Use Cases as Wrappers: Adding an Extra Layer
In many mobile apps, especially those with straightforward data requirements, use cases often end up acting as thin wrappers around repository calls, simply forwarding requests to the repository without additional business logic.
Here’s an example of a "wrapper" use case:
class GetPostsUseCase(private val postRepository: PostRepository) {
suspend operator fun invoke(): List<Post> {
// Just calls the repository, without any additional processing.
return postRepository.getPosts()
}
}
In this example, GetPostsUseCase
doesn’t provide any real value on top of what the repository already does. The repository itself could be directly accessed by the ViewModel, following the Repository Pattern, without sacrificing readability or maintainability. This pattern calls data from a centralized source and could be an ideal way to streamline code.
3. When to Consider Omitting Use Cases
In cases where a use case doesn’t add value beyond calling the repository, omitting it can simplify your codebase. Calling the data layer directly via a repository in these situations makes your code easier to read and maintain without sacrificing structure.
class ProfileViewModel(
private val userRepository: UserRepository
) : ViewModel() {
fun getUserProfile(userId: String) = liveData {
emit(userRepository.getUserProfile(userId)) // Directly calling the repository.
}
}
By directly using the UserRepository
, we reduce the need for an extra class and method that serves no additional purpose. For straightforward data access patterns, the repository pattern alone can keep the code well-organized while avoiding the verbosity and potential maintenance of unnecessary use cases.
Conclusion: Streamline Where Possible
While use cases can provide clarity and structure in complex applications with business logic-heavy features, they are often overused as simple data wrappers in mobile apps. In situations where they don’t contribute additional functionality or encapsulate business logic, omitting empty use cases can simplify your code. As always, choose what best fits your app’s needs and keep your code as lean as possible.
Posted on October 26, 2024
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.
Related
November 27, 2024