Skip to content

Use cases

Albert Pinto edited this page Jul 13, 2021 · 1 revision

To perform some actions with the application domain, we need to implement use cases. This documentation will implement the SendName use case, which stores the name in a database and returns it.

Use case interface

Every use case of the application has to be an indirect implementation of the UseCase interface. It needs to be passed the request and response types, which should implement the UseCase.UseCaseRequest and UseCase.UseCaseResponse interfaces, respectively. The only method within the interface is execute(), which executes the use case given the request and generates a UseCaseResult with the response.

Use case result

The UseCaseResult is a sealed class that contains the possible results of the execution of the use case:

  • Success: the use case is executed successfully and has the result as its property.
  • Error: there has been an error while executing the use case and has the exception as its property.

Defining a use case

Next, we will define the SendName use case. We will use an interface to decouple the use cases from the ViewModel. The interface has to extend from UseCase and define the request and response of the use case. The request and response will be data classes that implements the UseCase.UseCaseRequest and UseCase.UseCaseResponse interfaces, respectively. The names of those classes will be SendName.Request and SendName.Response.

interface SendName : UseCase<SendName.Request, SendName.Response> {
    data class Request(
        val name: String
    ) : UseCase.UseCaseRequest

    data class Response(
        val name: String
    ) : UseCase.UseCaseResponse
}

Implementing the use case

Next, we have to implement the SendName interface. The class will be called SendNameImpl and implement the execute() method from the UseCase interface.

Dependency injection

The communication between the use case and the data has to be done using the repository pattern. We have to define a repository class that will hide from the use case where the data is stored or provided. This communication has to be done using interfaces to decouple the layers, which will be passed using dependency injection.

class SendNameImpl @Inject constructor(
    private val nameRepository: NameRepository
) : SendName {
    override suspend fun execute(request: SendName.Request): UseCaseResult<SendName.Response> {
        // ...
    }
}

Implementing the execute method

The execute() method takes as a parameter a SendName.Request instance and returns a UseCaseResult<SendName.Response> object. In this case, the use case will only make a call to the repository with a name, which will return from it. If an exception is thrown while executing the use case, then the answer will be of type UseCaseResult.Error, and the exception will be stored as a parameter of it. Otherwise, the answer will be of type UseCaseResult.Success, and the result of the use case will be stored as a parameter. This suggests using a try-catch block, which will return the result depending on if an exception is thrown.

override suspend fun execute(request: SendName.Request): UseCaseResult<SendName.Response> {
    return try {
        val name = nameRepository.storeName(request.name)
        UseCaseResult.Success(SendName.Response(name))
    } catch (e: Exception) {
        UseCaseResult.Error(e)
    }
}

We can refactor the previous method to make it more Kotlin friendly.

override suspend fun execute(request: SendName.Request) = try {
    val name = nameRepository.storeName(request.name)
    UseCaseResult.Success(SendName.Response(name))
} catch (e: Exception) {
    UseCaseResult.Error(e)
}

Using the use case runner

The structure of all the implementations of use cases is similar, since all of them will be a try catch block as shown in the previous section. In order to improve readability there is a method called runUseCase() which will execute any use case and return the result as expected, depending on if it is a Success or an Error.

override suspend fun execute(request: SendName.Request) = runUseCase {
    val name = nameRepository.storeName(request.name)
    SendName.Response(name)
}