Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use event queue to avoid heap allocated coroutineScope #999

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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 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
* 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
Expand All @@ -60,7 +95,7 @@ class DefaultTaskRepository @Inject constructor(
id = taskId,
)
localDataSource.upsert(task.toLocal())
saveTasksToNetwork()
sendNetworkSyncEvent()
return taskId
}

Expand All @@ -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> {
Expand Down Expand Up @@ -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()
}

/**
Expand All @@ -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.
}
}
}
}
Loading