CodeNewbie Community 🌱

Cover image for Android code refactoring: clean code usecases
Tristan
Tristan

Posted on

Android code refactoring: clean code usecases

Introduction

  • In this series we are going to be talking about code refactoring, which is considered a more intermediate topic. So if you never have heard about UseCases or the Android clean code architecture, then this tutorial series might not be for you.
  • Also, I would like to point out that these patterns are really more like guide lines and not strict rules that you must abide by.

Resources

  • I found these refactoring patterns in the Greenstand Android GitHub repository. Specifically, I looked at these 3 files:

1) UsesCases Abstraction

2) No parameters UseCase

3) Multiple parameters UseCase

1) UsesCases Abstraction

  • So the UseCase abstraction looks like this:
abstract class UseCase<in Params : Any, out Result> {

    abstract suspend fun execute(params: Params): Result
}
Enter fullscreen mode Exit fullscreen mode
  • Just a little reminder, all Abstract classes in Kotlin are open by default and can not be instantiated but only inherited. We use this abstract class to implement the same behaviour across related UseCase objects, which allows us to establish a is a relationship.
  • The next thing I should point out is the in and out operators. Which are called contravariant and variance annotations. Without diving too deep into generics, we can understand that the in(contravariant) operator means it can only be consumed and never produced(not allowed as a return type). The out(variance) operator means it is to be only produced(used as a return type)

2) No parameters UseCase

  • So if we start with a UseCase code like:
class LogoutUseCase constructor(
    private val authRepository: AuthRepository
) {
    suspend fun invoke():Boolean{
        return authRepository.signUserOut()

    }
}

Enter fullscreen mode Exit fullscreen mode
  • Then we can implement our Abstract UseCase, resulting in:
class LogoutUseCase constructor(
    private val authRepository: AuthRepository
):UseCase<Unit,Boolean>() {

    override suspend fun execute(params: Unit): Boolean {
        return authRepository.signUserOut()
    }
}
Enter fullscreen mode Exit fullscreen mode
  • So lets talk about the Unit class being used in execute(params: Unit). Unit type fulfills the same function as Java's void. But what makes the two different is that Unit is a full fledged type and unlike void, it can be used as a type argument. Which makes it useful when combined with generics. To call the execute() method we would do so like this:
logoutUseCaseInstance.execute(Unit)
Enter fullscreen mode Exit fullscreen mode
  • We are able to do this with Unit because it is a singleton and returns the Unit object

3) Multiple parameters UseCase

  • Now we are going to understand how to use this pattern when we are faced with multiple parameters. So starting with a class like:
class CreateUserUseCase constructor(
    private val database:DatabaseRepository
){

    suspend operator fun invoke(email:String, username:String):Flow<Response<Actions>>{
        return database.createUser(email, username)
    }
}

Enter fullscreen mode Exit fullscreen mode
  • Then when we implement our pattern, we get:
data class CreateUserParams(
    val email: String,
    val username: String,
)

class CreateUserUseCase constructor(
    private val database:DatabaseRepository
):UseCase<CreateUserParams,Flow<Response<Actions>>>(){


    override suspend fun execute(params: CreateUserParams): Flow<Response<Actions>> {
        return database.createUser(params.email, params.username)
    }
}

Enter fullscreen mode Exit fullscreen mode
  • Notice the CreateUserParams class, this is how we are dealing with the problem of multiple parameters. By creating a data class we are able to handle any number of parameters which are needed.

  • While this might seem like extra work, what this refactoring really does is makes our code more readable and predictable.

Conclusion

  • Thank you for taking the time out of your day to read this blog post of mine. If you have any questions or concerns please comment below or reach out to me on Twitter.

Top comments (1)

Collapse
 
lesternixon profile image
Lesternixon

This refactoring actually makes our code more understandable and predictable, despite the fact that it may appear to be extra work.