0
0
Android Kotlinmobile~20 mins

Clean Architecture layers in Android Kotlin - Mini App: Build & Ship

Choose your learning style9 modes available
Build: Clean Architecture Demo
This screen demonstrates a simple app structured with Clean Architecture layers: Presentation, Domain, and Data. It shows a list of tasks fetched from a repository and allows adding a new task.
Target UI
-------------------------
| Clean Architecture Demo |
-------------------------
| Tasks:                 |
| 1. Buy groceries       |
| 2. Walk the dog        |
|                       |
| [Add Task]             |
-------------------------
Use three layers: Presentation (UI), Domain (business logic), Data (repository).
Presentation layer shows a list of tasks and an Add Task button.
Domain layer defines a UseCase to get tasks and add a task.
Data layer implements a repository with in-memory storage.
Add Task button adds a new task with a fixed name 'New Task'.
UI updates to show the new task after adding.
Starter Code
Android Kotlin
package com.example.cleanarchitecture

import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material3.Button
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp

// TODO: Define data layer repository interface and implementation

// TODO: Define domain layer use cases

// TODO: Define presentation layer UI

class MainActivity : ComponentActivity() {
  override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContent {
      MaterialTheme {
        Surface(modifier = Modifier.padding(16.dp)) {
          // TODO: Call the UI composable here
        }
      }
    }
  }
}
Task 1
Task 2
Task 3
Task 4
Task 5
Solution
Android Kotlin
package com.example.cleanarchitecture

import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material3.Button
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp

// Data Layer
// Task data class
data class Task(val id: Int, val name: String)

// Repository interface
interface TaskRepository {
  fun getTasks(): List<Task>
  fun addTask(task: Task)
}

// In-memory repository implementation
class InMemoryTaskRepository : TaskRepository {
  private val tasks = mutableListOf(
    Task(1, "Buy groceries"),
    Task(2, "Walk the dog")
  )

  override fun getTasks(): List<Task> = tasks.toList()

  override fun addTask(task: Task) {
    tasks.add(task)
  }
}

// Domain Layer
class GetTasksUseCase(private val repository: TaskRepository) {
  operator fun invoke(): List<Task> = repository.getTasks()
}

class AddTaskUseCase(private val repository: TaskRepository) {
  operator fun invoke(task: Task) {
    repository.addTask(task)
  }
}

// Presentation Layer
@Composable
fun TaskScreen(
  getTasksUseCase: GetTasksUseCase,
  addTaskUseCase: AddTaskUseCase
) {
  var tasks by remember { mutableStateOf(getTasksUseCase()) }

  Column {
    Text(text = "Tasks:", style = MaterialTheme.typography.titleMedium)
    LazyColumn(modifier = Modifier.padding(vertical = 8.dp)) {
      items(tasks) { task ->
        Text(text = "${task.id}. ${task.name}")
      }
    }
    Button(onClick = {
      val newTask = Task(tasks.size + 1, "New Task")
      addTaskUseCase(newTask)
      tasks = getTasksUseCase() // refresh list
    }) {
      Text("Add Task")
    }
  }
}

class MainActivity : ComponentActivity() {
  override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    val repository = InMemoryTaskRepository()
    val getTasksUseCase = GetTasksUseCase(repository)
    val addTaskUseCase = AddTaskUseCase(repository)

    setContent {
      MaterialTheme {
        Surface(modifier = Modifier.padding(16.dp)) {
          TaskScreen(getTasksUseCase, addTaskUseCase)
        }
      }
    }
  }
}

This solution uses Clean Architecture layers clearly separated:

  • Data Layer: Defines Task data class and TaskRepository interface with an in-memory implementation InMemoryTaskRepository that stores tasks in a list.
  • Domain Layer: Contains use cases GetTasksUseCase and AddTaskUseCase that interact with the repository to get and add tasks. This isolates business logic from UI and data.
  • Presentation Layer: The TaskScreen composable displays the list of tasks and an Add Task button. It uses Compose state to update the UI when tasks change. Clicking the button adds a new task with a fixed name and refreshes the list.

This structure helps keep code organized, testable, and easy to maintain by separating concerns.

Final Result
Completed Screen
-------------------------
| Clean Architecture Demo |
-------------------------
| Tasks:                 |
| 1. Buy groceries       |
| 2. Walk the dog        |
| 3. New Task            |
|                       |
| [Add Task]             |
-------------------------
User sees a list of tasks with their numbers and names.
User taps 'Add Task' button.
A new task named 'New Task' is added to the list and displayed immediately.
Stretch Goal
Add a text input field to let the user enter a custom task name before adding.
💡 Hint
Use a TextField composable with state to capture input and pass it to AddTaskUseCase instead of a fixed name.