Making Android Code Cleaner with Use Cases: A Practical Approach Using Kotlin Coroutines | by Siarhei Krupenich | Apr, 2025

Introduction

// Interactor
class UserInteractor {
private var username: String = "Guest"

fun getUsername(): String {
return username
}

fun saveUsername(name: String) {
username = name
}
}

// Presenter
class UserPresenter(
private val view: UserView,
private val interactor: UserInteractor
) {
fun loadUsername() {
val name = interactor.getUsername()
view.showUsername(name)
}

fun updateUsername(name: String) {
interactor.saveUsername(name)
view.showSavedMessage()
}
}

Diagram 1: UML Use Case
// Use-Case 1
class GetUserNameUseCase(
val repository: UserRepository,
) {
operator fun invoke(): String {
return repository.getUserName()
}
}

// Use-Case 2
class SaveUsernameUseCase(
val repository: UserRepository,
) {
operator fun invoke(name: String) {
repository.save(name)
}
}

// ViewModel
class ViewModel(
private val getUsername: GetUserNameUseCase,
private val saveUsername: SaveUsernameUseCase
) {
val userName: StateFlow

fun loadUsername() {
userName.update(getUsername())
}

fun updateUsername(name: String) {
saveUsername(name)
}
}

@Before
fun setup() {
useCase = GetUserNameUseCase(repository)
}

@Test
fun `should return username from repository`() {
`when`(repository.getUserName()).thenReturn("JohnDoe")
val result = useCase()

assertEquals("JohnDoe", result)
verify(repository).getUserName()
}

@Test
fun `should save username to repository`() {
val testName = "JaneDoe"
useCase(testName)

verify(repository).save(testName)
}

How this could be incorporated into a real Repos application

// A template interface as an abstraction for all Use-Cases
interface SuspendUseCase {
suspend operator fun invoke(param: T): O
}
// A particular Use-case interface
interface GetReposUseCase: SuspendUseCase>>
// A simple implementation of the GetReposUseCase
internal class GetReposUseCaseImpl(
private val mapper: Mapper,
private val repository: ReposRepository
) : GetReposUseCase {
override suspend operator fun invoke(param: Boolean):
ResultWithFallback> =
repository.getRepos(param).map(mapper::map)
}
// A template interface as an abstraction for all Use-Cases
interface SuspendUseCase {
suspend fun execute(param: T): O
}
// Extending the SuspendUseCase interface
interface RepositoryUseCase :
SuspendUseCase {

var repository: R
}

// Abstract class for ensuring following the contract
abstract class BaseRepositoryUseCase(override var repository: R) :
RepositoryUseCase {

suspend operator fun invoke(params: I? = null): O {
// here is a call of the SuspendUseCase inteface
return execute(params)
}
}

// Use-case implementation
class GetReposUseCase(repository: RepoRepository):
BaseRepositoryUseCase>, RepoRepository>(repository) {

override suspend fun execute(param: Boolean): ResultWithFallback> =
// obtaining and returning result using the repository like repository.getData()
}
}

Conclusion

Leave a Comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.