@@ -18,141 +18,138 @@ package com.example.android.architecture.blueprints.todoapp.data
18
18
19
19
import com.example.android.architecture.blueprints.todoapp.data.source.local.TasksDao
20
20
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
22
23
import kotlinx.coroutines.flow.Flow
23
24
import kotlinx.coroutines.flow.map
24
- import kotlinx.coroutines.launch
25
+ import kotlinx.coroutines.withContext
25
26
26
27
/* *
27
28
* 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.
28
35
*/
29
36
class DefaultTasksRepository (
30
37
private val tasksNetworkDataSource : NetworkDataSource ,
31
38
private val tasksDao : TasksDao ,
39
+ private val coroutineDispatcher : CoroutineDispatcher = Dispatchers .Default
32
40
) : TasksRepository {
33
41
34
42
override suspend fun createTask (title : String , description : String ): Task {
35
43
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()
43
46
return task
44
47
}
45
48
46
49
override suspend fun updateTask (taskId : String , title : String , description : String ) {
47
-
48
50
val task = getTask(taskId)?.copy(
49
51
title = title,
50
52
description = description
51
53
) ? : throw Exception (" Task (id $taskId ) not found" )
52
54
53
- coroutineScope {
54
- launch { tasksNetworkDataSource.saveTask(task.toNetworkModel()) }
55
- launch {
56
- tasksDao.insertTask(task.toLocalModel())
57
- }
58
- }
55
+ tasksDao.insertTask(task.toLocalModel())
56
+ saveTasksToNetwork()
59
57
}
60
58
61
59
override suspend fun getTasks (forceUpdate : Boolean ): List <Task > {
62
60
if (forceUpdate) {
63
- updateTasksFromRemoteDataSource()
61
+ loadTasksFromNetwork()
62
+ }
63
+ return withContext(coroutineDispatcher) {
64
+ tasksDao.getTasks().toExternalModels()
64
65
}
65
- return tasksDao.getTasks().map { it.toExternalModel() }
66
66
}
67
67
68
68
override suspend fun refreshTasks () {
69
- updateTasksFromRemoteDataSource ()
69
+ loadTasksFromNetwork ()
70
70
}
71
71
72
72
override fun getTasksStream (): Flow <List <Task >> {
73
73
return tasksDao.observeTasks().map { tasks ->
74
- tasks.map { task ->
75
- task.toExternalModel ()
74
+ withContext(coroutineDispatcher) {
75
+ tasks.toExternalModels ()
76
76
}
77
77
}
78
78
}
79
79
80
80
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()
92
82
}
93
83
94
84
override fun getTaskStream (taskId : String ): Flow <Task ?> {
95
85
return tasksDao.observeTaskById(taskId).map { it.toExternalModel() }
96
86
}
97
87
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
-
110
88
/* *
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.
113
90
*
114
91
* @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 .
116
93
*/
117
94
override suspend fun getTask (taskId : String , forceUpdate : Boolean ): Task ? {
118
95
if (forceUpdate) {
119
- updateTaskFromRemoteDataSource(taskId )
96
+ loadTasksFromNetwork( )
120
97
}
121
98
return tasksDao.getTaskById(taskId)?.toExternalModel()
122
99
}
123
100
124
101
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()
129
104
}
130
105
131
106
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()
136
109
}
137
110
138
111
override suspend fun clearCompletedTasks () {
139
- coroutineScope {
140
- launch { tasksNetworkDataSource.clearCompletedTasks() }
141
- launch { tasksDao.deleteCompletedTasks() }
142
- }
112
+ tasksDao.deleteCompletedTasks()
113
+ saveTasksToNetwork()
143
114
}
144
115
145
116
override suspend fun deleteAllTasks () {
146
- coroutineScope {
147
- launch { tasksNetworkDataSource.deleteAllTasks() }
148
- launch { tasksDao.deleteTasks() }
149
- }
117
+ tasksDao.deleteTasks()
118
+ saveTasksToNetwork()
150
119
}
151
120
152
121
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())
156
153
}
157
154
}
158
155
}
0 commit comments