Clean Architecture in the flavour of Jetpack Compose
Tags: android, programming, kotlin
Published: 2021-10-14 09:26:01

By employing clean architecture, you can design applications with very low coupling and independent of technical implementation details, such as databases and frameworks. That way, the application becomes easy to maintain and flexible to change. It also becomes intrinsically testable. Here I’ll show how I structure my clean architecture projects. This time we are going to build an Android todo application using Jetpack Compose. We’ll only illustrate one use case of listing todos retrieved from an API. Let’s get started.

The package structure of the project takes on the following form:

![Package Structure](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2vttzinkg3y2udzv47dg.png)


Starting from the bottom,

The **PRESENTATION** layer will keep all of the UI related code. In this case that would be the view and view model of the list of todos.

The **DOMAIN** layer keeps all business logic and gives the visitor to the project a good idea of what the code does and not how it does it. In this layer we have:

1. **UseCases**: One file per use case,
1. **Repository**: Repository interfaces
1. **Model**: Business models like Todo which we will reference in business logic use cases and our UI

The **DATA** layer:

1. **Repository**: Repository implementations
2. **DataSource**: All data source interfaces and data source entities. These entities are different from the domain models, and map directly to request and response objects from the api.

and lastly the **CORE** layer keep all the components which are common across all layers like constants or configs or dependency injection (which we won’t cover) 

![Files and Folder Stucture](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ljkit91pgzicov5cduqq.png)
 

Our first task would be always to start with the domain models and data entities

```kotlin
data class Todo(
    val id: Int,
    val isCompleted: Boolean,
    val task: String

)

```

```kotlin
data class TodoAPIEntity(
    val id: Int,
    val completed: Boolean,
    val title: String
)

fun TodoAPIEntity.toTodo(): Todo {
    return Todo(
        id = id,
        isCompleted = completed,
        task = title
    )
}
```


Let’s now write an interface for the TodoDatasource. We need one to enforce how any datasource (api, db, etc) needs to behave.

```kotlin
import za.co.nanosoft.cleantodo.Domain.Model.Todo 
interface TodoDataSource {    
    suspend fun getTodos(): List<Todo>
}
```

We have enough to write an implementation of this interface and we’ll call it TodoAPIImpl:

```kotlin

interface TodoApi {

    @GET("todos")
    suspend fun getTodos(): List<TodoAPIEntity>

    companion object {
        var todoApi: TodoApi? = null
        fun getInstance(): TodoApi {
            if (todoApi == null) {
                todoApi = Retrofit.Builder()
                    .baseUrl(BASE_URL)
                    .addConverterFactory(GsonConverterFactory.create())
                    .build().create(TodoApi::class.java)
            }
            return todoApi!!
        }
    }
}

class TodoAPIImpl : TodoDataSource {

    override suspend fun getTodos(): List<Todo> {
        return TodoAPI.getInstance().getTodos().map { it.toTodo() }
    }
}
```

**Note**: this repository’s getTodos function returns a list of Todo. So, we have to map TodoEntity -> Todo:

Before we write our TodoRepositoryImpl let’s write the interface for that in the Domain layer

```kotlin
interface TodoRepository {
    suspend fun getTodos(): List<Todo>

}
```

```kotlin
class TodoRepositoryImpl(private val datasource: TodoDataSource) : TodoRepository {

    override suspend fun getTodos(): List<Todo> {
        return datasource.getTodos()
    }
}

```

We can now see that the TodoRepositoryImpl can take any datasource as a dependency, great for swapping out datasources.

Now that we have our todo repository, we can code up the GetTodos use case

```kotlin
class GetTodos(
    private val repository: TodoRepository
) {
    suspend operator fun invoke(): List<Todo> {
        return repository.getTodos()
    }
}
```


and then in turn we can write our presentation’s view model and view

```kotlin
class TodoViewModel constructor(
    private val getTodosUseCase: GetTodos
) : ViewModel() {
    private val _todos = mutableStateListOf<Todo>()

    val todos: List<Todo>
        get() = _todos


    suspend fun getTodos() {
        viewModelScope.launch {
            _todos.addAll(getTodosUseCase())
        }
    }
}
```


```kotlin
@Composable
fun TodoListView(vm: TodoViewModel) {

    LaunchedEffect(Unit, block = {
        vm.getTodos()
    })

    Scaffold(
        topBar = {
            TopAppBar(
                title = {
                    Text("Todos")
                }
            )
        },
        content = {
            Column(modifier = Modifier.padding(16.dp)) {
                LazyColumn(modifier = Modifier.fillMaxHeight()) {
                    items(vm.todos) { todo ->
                        Row(modifier = Modifier.padding(16.dp)) {
                            Checkbox(checked = todo.isCompleted, onCheckedChange = null)
                            Spacer(Modifier.width(5.dp))
                            Text(todo.task)
                        }
                        Divider()
                    }
                }
            }
        }
    )
}
```


```kotlin
class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        val vm = TodoViewModel(
            getTodosUseCase = GetTodos(
                repository = TodoRepositoryImpl(
                    api = TodoAPIImpl()
                )
            )
        )
        super.onCreate(savedInstanceState)
        setContent {
            CleantodoTheme {
                TodoListView(vm)
            }
        }
    }
}
```
so to recap:

![Application Flow](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/m6fgzamu5m2sw7sp6y2c.png)

![List View Screen](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/nmdix4d4c7jvjx8pnpb7.png)