Skip to content

Commit e95f5f3

Browse files
author
github-actions
committed
Merge remote-tracking branch 'origin/main'
2 parents ef917fa + 0c31416 commit e95f5f3

File tree

6 files changed

+75
-145
lines changed

6 files changed

+75
-145
lines changed

app/src/main/java/com/example/android/architecture/blueprints/todoapp/data/DefaultTasksRepository.kt

Lines changed: 64 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -18,141 +18,138 @@ package com.example.android.architecture.blueprints.todoapp.data
1818

1919
import com.example.android.architecture.blueprints.todoapp.data.source.local.TasksDao
2020
import com.example.android.architecture.blueprints.todoapp.data.source.network.NetworkDataSource
21-
import kotlinx.coroutines.coroutineScope
21+
import kotlinx.coroutines.CoroutineDispatcher
22+
import kotlinx.coroutines.Dispatchers
2223
import kotlinx.coroutines.flow.Flow
2324
import kotlinx.coroutines.flow.map
24-
import kotlinx.coroutines.launch
25+
import kotlinx.coroutines.withContext
2526

2627
/**
2728
* Default implementation of [TasksRepository]. Single entry point for managing tasks' data.
29+
*
30+
* @param tasksNetworkDataSource - The network data source
31+
* @param tasksDao - The local data source
32+
* @param coroutineDispatcher - The dispatcher to be used for long running or complex operations,
33+
* such as network calls or mapping many models. This is important to avoid blocking the calling
34+
* thread.
2835
*/
2936
class DefaultTasksRepository(
3037
private val tasksNetworkDataSource: NetworkDataSource,
3138
private val tasksDao: TasksDao,
39+
private val coroutineDispatcher: CoroutineDispatcher = Dispatchers.Default
3240
) : TasksRepository {
3341

3442
override suspend fun createTask(title: String, description: String): Task {
3543
val task = Task(title = title, description = description)
36-
37-
coroutineScope {
38-
launch { tasksNetworkDataSource.saveTask(task.toNetworkModel()) }
39-
launch {
40-
tasksDao.insertTask(task.toLocalModel())
41-
}
42-
}
44+
tasksDao.insertTask(task.toLocalModel())
45+
saveTasksToNetwork()
4346
return task
4447
}
4548

4649
override suspend fun updateTask(taskId: String, title: String, description: String) {
47-
4850
val task = getTask(taskId)?.copy(
4951
title = title,
5052
description = description
5153
) ?: throw Exception("Task (id $taskId) not found")
5254

53-
coroutineScope {
54-
launch { tasksNetworkDataSource.saveTask(task.toNetworkModel()) }
55-
launch {
56-
tasksDao.insertTask(task.toLocalModel())
57-
}
58-
}
55+
tasksDao.insertTask(task.toLocalModel())
56+
saveTasksToNetwork()
5957
}
6058

6159
override suspend fun getTasks(forceUpdate: Boolean): List<Task> {
6260
if (forceUpdate) {
63-
updateTasksFromRemoteDataSource()
61+
loadTasksFromNetwork()
62+
}
63+
return withContext(coroutineDispatcher) {
64+
tasksDao.getTasks().toExternalModels()
6465
}
65-
return tasksDao.getTasks().map { it.toExternalModel() }
6666
}
6767

6868
override suspend fun refreshTasks() {
69-
updateTasksFromRemoteDataSource()
69+
loadTasksFromNetwork()
7070
}
7171

7272
override fun getTasksStream(): Flow<List<Task>> {
7373
return tasksDao.observeTasks().map { tasks ->
74-
tasks.map { task ->
75-
task.toExternalModel()
74+
withContext(coroutineDispatcher) {
75+
tasks.toExternalModels()
7676
}
7777
}
7878
}
7979

8080
override suspend fun refreshTask(taskId: String) {
81-
updateTaskFromRemoteDataSource(taskId)
82-
}
83-
84-
private suspend fun updateTasksFromRemoteDataSource() {
85-
val remoteTasks = tasksNetworkDataSource.loadTasks()
86-
87-
// Real apps might want to do a proper sync, deleting, modifying or adding each task.
88-
tasksDao.deleteTasks()
89-
remoteTasks.forEach { task ->
90-
tasksDao.insertTask(task.toTaskEntity())
91-
}
81+
loadTasksFromNetwork()
9282
}
9383

9484
override fun getTaskStream(taskId: String): Flow<Task?> {
9585
return tasksDao.observeTaskById(taskId).map { it.toExternalModel() }
9686
}
9787

98-
private suspend fun updateTaskFromRemoteDataSource(taskId: String) {
99-
val remoteTask = tasksNetworkDataSource.getTask(taskId)
100-
101-
if (remoteTask == null) {
102-
tasksDao.deleteTaskById(taskId)
103-
} else {
104-
tasksDao.insertTask(
105-
remoteTask.toTaskEntity()
106-
)
107-
}
108-
}
109-
11088
/**
111-
* Relies on [getTasks] to fetch data and picks the task with the same ID. Will return a null
112-
* Task if the task cannot be found.
89+
* Get a Task with the given ID. Will return null if the task cannot be found.
11390
*
11491
* @param taskId - The ID of the task
115-
* @param forceUpdate - true if the task should be updated from the remote data source.
92+
* @param forceUpdate - true if the task should be updated from the network data source first.
11693
*/
11794
override suspend fun getTask(taskId: String, forceUpdate: Boolean): Task? {
11895
if (forceUpdate) {
119-
updateTaskFromRemoteDataSource(taskId)
96+
loadTasksFromNetwork()
12097
}
12198
return tasksDao.getTaskById(taskId)?.toExternalModel()
12299
}
123100

124101
override suspend fun completeTask(taskId: String) {
125-
coroutineScope {
126-
launch { tasksNetworkDataSource.completeTask(taskId) }
127-
launch { tasksDao.updateCompleted(taskId = taskId, completed = true) }
128-
}
102+
tasksDao.updateCompleted(taskId = taskId, completed = true)
103+
saveTasksToNetwork()
129104
}
130105

131106
override suspend fun activateTask(taskId: String) {
132-
coroutineScope {
133-
launch { tasksNetworkDataSource.activateTask(taskId) }
134-
launch { tasksDao.updateCompleted(taskId = taskId, completed = false) }
135-
}
107+
tasksDao.updateCompleted(taskId = taskId, completed = false)
108+
saveTasksToNetwork()
136109
}
137110

138111
override suspend fun clearCompletedTasks() {
139-
coroutineScope {
140-
launch { tasksNetworkDataSource.clearCompletedTasks() }
141-
launch { tasksDao.deleteCompletedTasks() }
142-
}
112+
tasksDao.deleteCompletedTasks()
113+
saveTasksToNetwork()
143114
}
144115

145116
override suspend fun deleteAllTasks() {
146-
coroutineScope {
147-
launch { tasksNetworkDataSource.deleteAllTasks() }
148-
launch { tasksDao.deleteTasks() }
149-
}
117+
tasksDao.deleteTasks()
118+
saveTasksToNetwork()
150119
}
151120

152121
override suspend fun deleteTask(taskId: String) {
153-
coroutineScope {
154-
launch { tasksNetworkDataSource.deleteTask(taskId) }
155-
launch { tasksDao.deleteTaskById(taskId) }
122+
tasksDao.deleteTaskById(taskId)
123+
saveTasksToNetwork()
124+
}
125+
126+
/**
127+
* The following methods load tasks from, and save tasks to, the network.
128+
*
129+
* Consider these to be long running operations, hence the need for `withContext` which
130+
* can change the coroutine dispatcher so that the caller isn't blocked.
131+
*
132+
* Real apps may want to do a proper sync, rather than the "one-way sync everything" approach
133+
* below. See https://developer.android.com/topic/architecture/data-layer/offline-first
134+
* for more efficient and robust synchronisation strategies.
135+
*
136+
* Also, in a real app, these operations could be scheduled using WorkManager.
137+
*/
138+
private suspend fun loadTasksFromNetwork() {
139+
withContext(coroutineDispatcher) {
140+
val remoteTasks = tasksNetworkDataSource.loadTasks()
141+
142+
tasksDao.deleteTasks()
143+
remoteTasks.forEach { task ->
144+
tasksDao.insertTask(task.toTaskEntity())
145+
}
146+
}
147+
}
148+
149+
private suspend fun saveTasksToNetwork() {
150+
withContext(coroutineDispatcher) {
151+
val localTasks = tasksDao.getTasks()
152+
tasksNetworkDataSource.saveTasks(localTasks.toNetworkModels())
156153
}
157154
}
158155
}

app/src/main/java/com/example/android/architecture/blueprints/todoapp/data/ModelMappingExt.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,8 @@ fun LocalTask.toNetworkModel() = NetworkTask(
7777
status = if (isCompleted) { TaskStatus.COMPLETE } else { TaskStatus.ACTIVE }
7878
)
7979

80+
fun List.toNetworkModels() = map(LocalTask::toNetworkModel)
81+
8082
// External to Network
8183
fun Task.toNetworkModel() = toLocalModel().toNetworkModel()
8284

app/src/main/java/com/example/android/architecture/blueprints/todoapp/data/source/network/NetworkDataSource.kt

Lines changed: 1 addition & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -24,17 +24,5 @@ interface NetworkDataSource {
2424

2525
suspend fun loadTasks(): List<NetworkTask>
2626

27-
suspend fun getTask(taskId: String): NetworkTask?
28-
29-
suspend fun saveTask(task: NetworkTask)
30-
31-
suspend fun completeTask(taskId: String)
32-
33-
suspend fun activateTask(taskId: String)
34-
35-
suspend fun clearCompletedTasks()
36-
37-
suspend fun deleteAllTasks()
38-
39-
suspend fun deleteTask(taskId: String)
27+
suspend fun saveTasks(tasks: List<NetworkTask>)
4028
}

app/src/main/java/com/example/android/architecture/blueprints/todoapp/data/source/network/TasksNetworkDataSource.kt

Lines changed: 3 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -48,44 +48,15 @@ object TasksNetworkDataSource : NetworkDataSource {
4848
return tasks
4949
}
5050

51-
override suspend fun getTask(taskId: String): NetworkTask? {
51+
override suspend fun saveTasks(tasks: List<NetworkTask>) {
5252
// Simulate network by delaying the execution.
5353
delay(SERVICE_LATENCY_IN_MILLIS)
54-
return TASKS_SERVICE_DATA[taskId]
54+
TASKS_SERVICE_DATA.clear()
55+
TASKS_SERVICE_DATA.putAll(tasks.associateBy(NetworkTask::id))
5556
}
5657

5758
private fun addTask(id: String, title: String, shortDescription: String) {
5859
val newTask = NetworkTask(id = id, title = title, shortDescription = shortDescription)
5960
TASKS_SERVICE_DATA[newTask.id] = newTask
6061
}
61-
62-
override suspend fun saveTask(task: NetworkTask) {
63-
TASKS_SERVICE_DATA[task.id] = task
64-
}
65-
66-
override suspend fun completeTask(taskId: String) {
67-
TASKS_SERVICE_DATA[taskId]?.let {
68-
saveTask(it.copy(status = TaskStatus.COMPLETE))
69-
}
70-
}
71-
72-
override suspend fun activateTask(taskId: String) {
73-
TASKS_SERVICE_DATA[taskId]?.let {
74-
saveTask(it.copy(status = TaskStatus.ACTIVE))
75-
}
76-
}
77-
78-
override suspend fun clearCompletedTasks() {
79-
TASKS_SERVICE_DATA = TASKS_SERVICE_DATA.filterValues {
80-
it.status == TaskStatus.COMPLETE
81-
} as LinkedHashMap<String, NetworkTask>
82-
}
83-
84-
override suspend fun deleteAllTasks() {
85-
TASKS_SERVICE_DATA.clear()
86-
}
87-
88-
override suspend fun deleteTask(taskId: String) {
89-
TASKS_SERVICE_DATA.remove(taskId)
90-
}
9162
}

app/src/test/java/com/example/android/architecture/blueprints/todoapp/data/DefaultTasksRepositoryTest.kt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -245,9 +245,9 @@ class DefaultTasksRepositoryTest {
245245
@Test
246246
fun clearCompletedTasks() = runTest {
247247
val completedTask = task1.copy(isCompleted = true)
248-
tasksNetworkDataSource.tasks = mutableListOf(
249-
completedTask.toNetworkModel(),
250-
task2.toNetworkModel()
248+
tasksLocalDataSource.tasks = mutableListOf(
249+
completedTask.toLocalModel(),
250+
task2.toLocalModel()
251251
)
252252
tasksRepository.clearCompletedTasks()
253253

shared-test/src/main/java/com/example/android/architecture/blueprints/todoapp/data/source/network/FakeNetworkDataSource.kt

Lines changed: 2 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -21,35 +21,7 @@ class FakeNetworkDataSource(
2121
) : NetworkDataSource {
2222
override suspend fun loadTasks() = tasks ?: throw Exception("Task list is null")
2323

24-
override suspend fun getTask(taskId: String) = tasks?.firstOrNull { it.id == taskId }
25-
26-
override suspend fun saveTask(task: NetworkTask) {
27-
tasks?.add(task)
28-
}
29-
30-
override suspend fun completeTask(taskId: String) {
31-
tasks?.firstOrNull { it.id == taskId }?.let {
32-
deleteTask(it.id)
33-
saveTask(it.copy(status = TaskStatus.COMPLETE))
34-
}
35-
}
36-
37-
override suspend fun activateTask(taskId: String) {
38-
tasks?.firstOrNull { it.id == taskId }?.let {
39-
deleteTask(it.id)
40-
saveTask(it.copy(status = TaskStatus.ACTIVE))
41-
}
42-
}
43-
44-
override suspend fun clearCompletedTasks() {
45-
tasks?.removeIf { it.status == TaskStatus.COMPLETE }
46-
}
47-
48-
override suspend fun deleteAllTasks() {
49-
tasks?.clear()
50-
}
51-
52-
override suspend fun deleteTask(taskId: String) {
53-
tasks?.removeIf { it.id == taskId }
24+
override suspend fun saveTasks(tasks: List<NetworkTask>) {
25+
this.tasks = tasks.toMutableList()
5426
}
5527
}

0 commit comments

Comments
 (0)