From fc349fcfc12f3034f70b0884c7ea2c03e3d81472 Mon Sep 17 00:00:00 2001
From: Don Turner <donturner@google.com>
Date: Fri, 5 Apr 2024 14:53:32 +0100
Subject: [PATCH 1/2] Use event queue to avoid heap allocated coroutineScope

---
 .../todoapp/data/DefaultTaskRepository.kt     | 74 +++++++++++--------
 1 file changed, 43 insertions(+), 31 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 9fa32de87..3f6bcf5ef 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
@@ -25,6 +25,7 @@ import javax.inject.Inject
 import javax.inject.Singleton
 import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.channels.Channel
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.launch
@@ -45,9 +46,43 @@ class DefaultTaskRepository @Inject constructor(
     private val networkDataSource: NetworkDataSource,
     private val localDataSource: TaskDao,
     @DefaultDispatcher private val dispatcher: CoroutineDispatcher,
-    @ApplicationScope private val scope: CoroutineScope,
+    @ApplicationScope scope: CoroutineScope,
 ) : TaskRepository {
 
+    private val networkSyncEventQueue = Channel<Unit>()
+    init {
+        scope.launch {
+            for (syncEvent in networkSyncEventQueue) saveTasksToNetwork()
+        }
+    }
+
+    /**
+     * Send the tasks from the local data source to the network data sources.
+     *
+     * Real apps may want to use WorkManager to schedule this work, and provide a mechanism for
+     * failures to be communicated back to the user so that they are aware that their data isn't
+     * being backed up.
+     * */
+    private suspend fun saveTasksToNetwork() {
+        try {
+            val localTasks = localDataSource.getAll()
+            val networkTasks = withContext(dispatcher) {
+                localTasks.toNetwork()
+            }
+            networkDataSource.saveTasks(networkTasks)
+        } catch (e: Exception) {
+            // In a real app you'd handle the exception e.g. by exposing a `networkStatus` flow
+            // to an app level UI state holder which could then display a Toast message.
+        }
+    }
+
+    /**
+     * Inform this repository that the local data needs sending to the network.
+     */
+    private fun sendNetworkSyncEvent() {
+        networkSyncEventQueue.trySend(Unit)
+    }
+
     override suspend fun createTask(title: String, description: String): String {
         // ID creation might be a complex operation so it's executed using the supplied
         // coroutine dispatcher
@@ -60,7 +95,7 @@ class DefaultTaskRepository @Inject constructor(
             id = taskId,
         )
         localDataSource.upsert(task.toLocal())
-        saveTasksToNetwork()
+        sendNetworkSyncEvent()
         return taskId
     }
 
@@ -71,7 +106,7 @@ class DefaultTaskRepository @Inject constructor(
         ) ?: throw Exception("Task (id $taskId) not found")
 
         localDataSource.upsert(task.toLocal())
-        saveTasksToNetwork()
+        sendNetworkSyncEvent()
     }
 
     override suspend fun getTasks(forceUpdate: Boolean): List<Task> {
@@ -114,27 +149,27 @@ class DefaultTaskRepository @Inject constructor(
 
     override suspend fun completeTask(taskId: String) {
         localDataSource.updateCompleted(taskId = taskId, completed = true)
-        saveTasksToNetwork()
+        sendNetworkSyncEvent()
     }
 
     override suspend fun activateTask(taskId: String) {
         localDataSource.updateCompleted(taskId = taskId, completed = false)
-        saveTasksToNetwork()
+        sendNetworkSyncEvent()
     }
 
     override suspend fun clearCompletedTasks() {
         localDataSource.deleteCompleted()
-        saveTasksToNetwork()
+        sendNetworkSyncEvent()
     }
 
     override suspend fun deleteAllTasks() {
         localDataSource.deleteAll()
-        saveTasksToNetwork()
+        sendNetworkSyncEvent()
     }
 
     override suspend fun deleteTask(taskId: String) {
         localDataSource.deleteById(taskId)
-        saveTasksToNetwork()
+        sendNetworkSyncEvent()
     }
 
     /**
@@ -161,27 +196,4 @@ class DefaultTaskRepository @Inject constructor(
             localDataSource.upsertAll(remoteTasks.toLocal())
         }
     }
-
-    /**
-     * Send the tasks from the local data source to the network data source
-     *
-     * Returns immediately after launching the job. Real apps may want to suspend here until the
-     * operation is complete or (better) use WorkManager to schedule this work. Both approaches
-     * should provide a mechanism for failures to be communicated back to the user so that
-     * they are aware that their data isn't being backed up.
-     */
-    private fun saveTasksToNetwork() {
-        scope.launch {
-            try {
-                val localTasks = localDataSource.getAll()
-                val networkTasks = withContext(dispatcher) {
-                    localTasks.toNetwork()
-                }
-                networkDataSource.saveTasks(networkTasks)
-            } catch (e: Exception) {
-                // In a real app you'd handle the exception e.g. by exposing a `networkStatus` flow
-                // to an app level UI state holder which could then display a Toast message.
-            }
-        }
-    }
 }

From d291bcc682c39b58bdcbc48e729aa976ff3296e2 Mon Sep 17 00:00:00 2001
From: Don Turner <donturner@google.com>
Date: Fri, 5 Apr 2024 14:59:27 +0100
Subject: [PATCH 2/2] Fix typo

---
 .../blueprints/todoapp/data/DefaultTaskRepository.kt            | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

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 3f6bcf5ef..81c1c03e5 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
@@ -57,7 +57,7 @@ class DefaultTaskRepository @Inject constructor(
     }
 
     /**
-     * Send the tasks from the local data source to the network data sources.
+     * Send the tasks from the local data source to the network data source.
      *
      * Real apps may want to use WorkManager to schedule this work, and provide a mechanism for
      * failures to be communicated back to the user so that they are aware that their data isn't