From 7313121da0d449b0ce3a1fd368382b13319df84c Mon Sep 17 00:00:00 2001 From: Don Turner Date: Thu, 23 Mar 2023 15:25:34 +0000 Subject: [PATCH] Move Task ID creation out of Task (#924) --- .../todoapp/data/DefaultTaskRepository.kt | 12 ++++- .../blueprints/todoapp/data/Task.kt | 4 +- .../todoapp/taskdetail/TaskDetailScreen.kt | 21 ++++++-- .../blueprints/todoapp/tasks/TasksScreen.kt | 48 ++++++++++++++++--- .../todoapp/data/DefaultTaskRepositoryTest.kt | 8 ++-- .../todoapp/statistics/StatisticsUtilsTest.kt | 24 +++++++--- .../statistics/StatisticsViewModelTest.kt | 8 ++-- .../todoapp/tasks/TasksViewModelTest.kt | 10 ++-- .../todoapp/data/FakeTaskRepository.kt | 9 +++- 9 files changed, 109 insertions(+), 35 deletions(-) diff --git a/app/src/main/java/com/example/android/architecture/blueprints/todoapp/data/DefaultTaskRepository.kt b/app/src/main/java/com/example/android/architecture/blueprints/todoapp/data/DefaultTaskRepository.kt index ac73acac6..b22326b76 100644 --- a/app/src/main/java/com/example/android/architecture/blueprints/todoapp/data/DefaultTaskRepository.kt +++ b/app/src/main/java/com/example/android/architecture/blueprints/todoapp/data/DefaultTaskRepository.kt @@ -18,6 +18,7 @@ package com.example.android.architecture.blueprints.todoapp.data import com.example.android.architecture.blueprints.todoapp.data.source.local.TaskDao import com.example.android.architecture.blueprints.todoapp.data.source.network.NetworkDataSource +import java.util.UUID import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.Flow @@ -40,7 +41,16 @@ class DefaultTaskRepository( ) : TaskRepository { override suspend fun createTask(title: String, description: String): Task { - val task = Task(title = title, description = description) + // ID creation might be a complex operation so it's executed using the supplied + // coroutine dispatcher + val taskId = withContext(coroutineDispatcher) { + UUID.randomUUID().toString() + } + val task = Task( + title = title, + description = description, + id = taskId, + ) taskDao.upsert(task.toLocal()) saveTasksToNetwork() return task diff --git a/app/src/main/java/com/example/android/architecture/blueprints/todoapp/data/Task.kt b/app/src/main/java/com/example/android/architecture/blueprints/todoapp/data/Task.kt index 5a8457765..91377071c 100644 --- a/app/src/main/java/com/example/android/architecture/blueprints/todoapp/data/Task.kt +++ b/app/src/main/java/com/example/android/architecture/blueprints/todoapp/data/Task.kt @@ -16,8 +16,6 @@ package com.example.android.architecture.blueprints.todoapp.data -import java.util.UUID - /** * Immutable model class for a Task. * @@ -33,7 +31,7 @@ data class Task( val title: String = "", val description: String = "", val isCompleted: Boolean = false, - val id: String = UUID.randomUUID().toString() + val id: String, ) { val titleForList: String diff --git a/app/src/main/java/com/example/android/architecture/blueprints/todoapp/taskdetail/TaskDetailScreen.kt b/app/src/main/java/com/example/android/architecture/blueprints/todoapp/taskdetail/TaskDetailScreen.kt index 192b2c3d5..814377da3 100644 --- a/app/src/main/java/com/example/android/architecture/blueprints/todoapp/taskdetail/TaskDetailScreen.kt +++ b/app/src/main/java/com/example/android/architecture/blueprints/todoapp/taskdetail/TaskDetailScreen.kt @@ -156,7 +156,12 @@ private fun EditTaskContentPreview() { EditTaskContent( loading = false, empty = false, - Task("Title", "Description", isCompleted = false), + Task( + title = "Title", + description = "Description", + isCompleted = false, + id = "ID" + ), onTaskCheck = { }, onRefresh = { } ) @@ -172,7 +177,12 @@ private fun EditTaskContentTaskCompletedPreview() { EditTaskContent( loading = false, empty = false, - Task("Title", "Description", isCompleted = true), + Task( + title = "Title", + description = "Description", + isCompleted = false, + id = "ID" + ), onTaskCheck = { }, onRefresh = { } ) @@ -188,7 +198,12 @@ private fun EditTaskContentEmptyPreview() { EditTaskContent( loading = false, empty = true, - Task("Title", "Description", isCompleted = false), + Task( + title = "Title", + description = "Description", + isCompleted = false, + id = "ID" + ), onTaskCheck = { }, onRefresh = { } ) diff --git a/app/src/main/java/com/example/android/architecture/blueprints/todoapp/tasks/TasksScreen.kt b/app/src/main/java/com/example/android/architecture/blueprints/todoapp/tasks/TasksScreen.kt index fd55f2cb9..16cf81e86 100644 --- a/app/src/main/java/com/example/android/architecture/blueprints/todoapp/tasks/TasksScreen.kt +++ b/app/src/main/java/com/example/android/architecture/blueprints/todoapp/tasks/TasksScreen.kt @@ -236,11 +236,36 @@ private fun TasksContentPreview() { TasksContent( loading = false, tasks = listOf( - Task("Title 1", "Description 1"), - Task("Title 2", "Description 2", true), - Task("Title 3", "Description 3", true), - Task("Title 4", "Description 4"), - Task("Title 5", "Description 5", true) + Task( + title = "Title 1", + description = "Description 1", + isCompleted = false, + id = "ID 1" + ), + Task( + title = "Title 2", + description = "Description 2", + isCompleted = true, + id = "ID 2" + ), + Task( + title = "Title 3", + description = "Description 3", + isCompleted = true, + id = "ID 3" + ), + Task( + title = "Title 4", + description = "Description 4", + isCompleted = false, + id = "ID 4" + ), + Task( + title = "Title 5", + description = "Description 5", + isCompleted = true, + id = "ID 5" + ), ), currentFilteringLabel = R.string.label_all, noTasksLabel = R.string.no_tasks_all, @@ -291,7 +316,11 @@ private fun TaskItemPreview() { AppCompatTheme { Surface { TaskItem( - task = Task("Title", "Description"), + task = Task( + title = "Title", + description = "Description", + id = "ID" + ), onTaskClick = { }, onCheckedChange = { } ) @@ -305,7 +334,12 @@ private fun TaskItemCompletedPreview() { AppCompatTheme { Surface { TaskItem( - task = Task("Title", "Description", true), + task = Task( + title = "Title", + description = "Description", + isCompleted = true, + id = "ID" + ), onTaskClick = { }, onCheckedChange = { } ) diff --git a/app/src/test/java/com/example/android/architecture/blueprints/todoapp/data/DefaultTaskRepositoryTest.kt b/app/src/test/java/com/example/android/architecture/blueprints/todoapp/data/DefaultTaskRepositoryTest.kt index c4763d9c3..f21218201 100644 --- a/app/src/test/java/com/example/android/architecture/blueprints/todoapp/data/DefaultTaskRepositoryTest.kt +++ b/app/src/test/java/com/example/android/architecture/blueprints/todoapp/data/DefaultTaskRepositoryTest.kt @@ -33,10 +33,10 @@ import org.junit.Test @ExperimentalCoroutinesApi class DefaultTaskRepositoryTest { - private val task1 = Task(title = "Title1", description = "Description1") - private val task2 = Task(title = "Title2", description = "Description2") - private val task3 = Task(title = "Title3", description = "Description3") - private val newTask = Task(title = "Title new", description = "Description new") + private val task1 = Task(id = "1", title = "Title1", description = "Description1") + private val task2 = Task(id = "2", title = "Title2", description = "Description2") + private val task3 = Task(id = "3", title = "Title3", description = "Description3") + private val newTask = Task(id = "new", title = "Title new", description = "Description new") private val networkTasks = listOf(task1, task2).toNetwork().sortedBy { it.id } private val localTasks = listOf(task3.toLocal()).sortedBy { it.id } diff --git a/app/src/test/java/com/example/android/architecture/blueprints/todoapp/statistics/StatisticsUtilsTest.kt b/app/src/test/java/com/example/android/architecture/blueprints/todoapp/statistics/StatisticsUtilsTest.kt index 883d2fa46..d35aab169 100644 --- a/app/src/test/java/com/example/android/architecture/blueprints/todoapp/statistics/StatisticsUtilsTest.kt +++ b/app/src/test/java/com/example/android/architecture/blueprints/todoapp/statistics/StatisticsUtilsTest.kt @@ -29,7 +29,12 @@ class StatisticsUtilsTest { @Test fun getActiveAndCompletedStats_noCompleted() { val tasks = listOf( - Task("title", "desc", isCompleted = false) + Task( + id = "id", + title = "title", + description = "desc", + isCompleted = false, + ) ) // When the list of tasks is computed with an active task val result = getActiveAndCompletedStats(tasks) @@ -42,7 +47,12 @@ class StatisticsUtilsTest { @Test fun getActiveAndCompletedStats_noActive() { val tasks = listOf( - Task("title", "desc", isCompleted = true) + Task( + id = "id", + title = "title", + description = "desc", + isCompleted = true, + ) ) // When the list of tasks is computed with a completed task val result = getActiveAndCompletedStats(tasks) @@ -56,11 +66,11 @@ class StatisticsUtilsTest { fun getActiveAndCompletedStats_both() { // Given 3 completed tasks and 2 active tasks val tasks = listOf( - Task("title", "desc", isCompleted = true), - Task("title", "desc", isCompleted = true), - Task("title", "desc", isCompleted = true), - Task("title", "desc", isCompleted = false), - Task("title", "desc", isCompleted = false) + Task(id = "1", title = "title", description = "desc", isCompleted = true), + Task(id = "2", title = "title", description = "desc", isCompleted = true), + Task(id = "3", title = "title", description = "desc", isCompleted = true), + Task(id = "4", title = "title", description = "desc", isCompleted = false), + Task(id = "5", title = "title", description = "desc", isCompleted = false), ) // When the list of tasks is computed val result = getActiveAndCompletedStats(tasks) diff --git a/app/src/test/java/com/example/android/architecture/blueprints/todoapp/statistics/StatisticsViewModelTest.kt b/app/src/test/java/com/example/android/architecture/blueprints/todoapp/statistics/StatisticsViewModelTest.kt index 3de618073..532c70301 100644 --- a/app/src/test/java/com/example/android/architecture/blueprints/todoapp/statistics/StatisticsViewModelTest.kt +++ b/app/src/test/java/com/example/android/architecture/blueprints/todoapp/statistics/StatisticsViewModelTest.kt @@ -67,10 +67,10 @@ class StatisticsViewModelTest { @Test fun loadNonEmptyTasksFromRepository_NonEmptyResults() = runTest { // We initialise the tasks to 3, with one active and two completed - val task1 = Task("Title1", "Description1") - val task2 = Task("Title2", "Description2", true) - val task3 = Task("Title3", "Description3", true) - val task4 = Task("Title4", "Description4", true) + val task1 = Task(id = "1", title = "Title1", description = "Desc1") + val task2 = Task(id = "2", title = "Title2", description = "Desc2", isCompleted = true) + val task3 = Task(id = "3", title = "Title3", description = "Desc3", isCompleted = true) + val task4 = Task(id = "4", title = "Title4", description = "Desc4", isCompleted = true) tasksRepository.addTasks(task1, task2, task3, task4) // Then the results are not empty diff --git a/app/src/test/java/com/example/android/architecture/blueprints/todoapp/tasks/TasksViewModelTest.kt b/app/src/test/java/com/example/android/architecture/blueprints/todoapp/tasks/TasksViewModelTest.kt index 16a7b970c..97cc75c08 100644 --- a/app/src/test/java/com/example/android/architecture/blueprints/todoapp/tasks/TasksViewModelTest.kt +++ b/app/src/test/java/com/example/android/architecture/blueprints/todoapp/tasks/TasksViewModelTest.kt @@ -57,9 +57,9 @@ class TasksViewModelTest { fun setupViewModel() { // We initialise the tasks to 3, with one active and two completed tasksRepository = FakeTaskRepository() - val task1 = Task("Title1", "Description1") - val task2 = Task("Title2", "Description2", true) - val task3 = Task("Title3", "Description3", true) + val task1 = Task(id = "1", title = "Title1", description = "Desc1") + val task2 = Task(id = "2", title = "Title2", description = "Desc2", isCompleted = true) + val task3 = Task(id = "3", title = "Title3", description = "Desc3", isCompleted = true) tasksRepository.addTasks(task1, task2, task3) tasksViewModel = TasksViewModel(tasksRepository, SavedStateHandle()) @@ -195,7 +195,7 @@ class TasksViewModelTest { @Test fun completeTask_dataAndSnackbarUpdated() = runTest { // With a repository that has an active task - val task = Task("Title", "Description") + val task = Task(id = "id", title = "Title", description = "Description") tasksRepository.addTasks(task) // Complete task @@ -212,7 +212,7 @@ class TasksViewModelTest { @Test fun activateTask_dataAndSnackbarUpdated() = runTest { // With a repository that has a completed task - val task = Task("Title", "Description", true) + val task = Task(id = "id", title = "Title", description = "Description", isCompleted = true) tasksRepository.addTasks(task) // Activate task diff --git a/shared-test/src/main/java/com/example/android/architecture/blueprints/todoapp/data/FakeTaskRepository.kt b/shared-test/src/main/java/com/example/android/architecture/blueprints/todoapp/data/FakeTaskRepository.kt index 22e63662c..239d01361 100644 --- a/shared-test/src/main/java/com/example/android/architecture/blueprints/todoapp/data/FakeTaskRepository.kt +++ b/shared-test/src/main/java/com/example/android/architecture/blueprints/todoapp/data/FakeTaskRepository.kt @@ -17,6 +17,7 @@ package com.example.android.architecture.blueprints.todoapp.data import androidx.annotation.VisibleForTesting +import java.util.UUID import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow @@ -56,7 +57,11 @@ class FakeTaskRepository : TaskRepository { } override suspend fun createTask(title: String, description: String): Task { - return Task(title = title, description = description).also { + return Task( + title = title, + description = description, + id = generateTaskId() + ).also { saveTask(it) } } @@ -134,6 +139,8 @@ class FakeTaskRepository : TaskRepository { } } + private fun generateTaskId() = UUID.randomUUID().toString() + @VisibleForTesting fun addTasks(vararg tasks: Task) { _savedTasks.update { oldTasks ->