Skip to content

Commit

Permalink
Perform I/O operations in the background
Browse files Browse the repository at this point in the history
  • Loading branch information
Felix committed Oct 5, 2024
1 parent 6927139 commit 1f026a8
Show file tree
Hide file tree
Showing 9 changed files with 75 additions and 53 deletions.
1 change: 1 addition & 0 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ sqldelight {
databases {
create("Database") {
packageName.set("nl.ndat.tvlauncher.data.sqldelight")
generateAsync.set(true)
}
}
}
Expand Down
11 changes: 6 additions & 5 deletions app/src/main/kotlin/nl/ndat/tvlauncher/data/DatabaseContainer.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@ package nl.ndat.tvlauncher.data
import android.content.Context
import app.cash.sqldelight.ColumnAdapter
import app.cash.sqldelight.EnumColumnAdapter
import app.cash.sqldelight.TransactionWithReturn
import app.cash.sqldelight.TransactionWithoutReturn
import app.cash.sqldelight.SuspendingTransactionWithReturn
import app.cash.sqldelight.SuspendingTransactionWithoutReturn
import app.cash.sqldelight.async.coroutines.synchronous
import app.cash.sqldelight.driver.android.AndroidSqliteDriver
import nl.ndat.tvlauncher.data.sqldelight.Channel
import nl.ndat.tvlauncher.data.sqldelight.ChannelProgram
Expand All @@ -18,7 +19,7 @@ class DatabaseContainer(
const val DB_FILE = "data.db"
}

private val driver = AndroidSqliteDriver(Database.Schema, context, DB_FILE)
private val driver = AndroidSqliteDriver(Database.Schema.synchronous(), context, DB_FILE)
private val database = Database(
driver = driver,
ChannelAdapter = Channel.Adapter(
Expand All @@ -43,8 +44,8 @@ class DatabaseContainer(
val channels = database.channelQueries
val channelPrograms = database.channelProgramQueries

fun transaction(body: TransactionWithoutReturn.() -> Unit) = database.transaction { body() }
fun <T> transactionForResult(body: TransactionWithReturn<T>.() -> T) = database.transactionWithResult { body() }
suspend fun transaction(body: suspend SuspendingTransactionWithoutReturn.() -> Unit) = database.transaction { body() }
suspend fun <T> transactionForResult(body: suspend SuspendingTransactionWithReturn<T>.() -> T) = database.transactionWithResult { body() }
}

class IntColumnAdapter : ColumnAdapter<Int, Long> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package nl.ndat.tvlauncher.data

import app.cash.sqldelight.Query
import app.cash.sqldelight.coroutines.asFlow
import kotlinx.coroutines.flow.map
import app.cash.sqldelight.coroutines.mapToList
import kotlinx.coroutines.Dispatchers

fun <T : Any> Query<T>.executeAsListFlow() = asFlow().map { it.executeAsList() }
fun <T : Any> Query<T>.executeAsListFlow() = asFlow().mapToList(Dispatchers.IO)
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package nl.ndat.tvlauncher.data.repository

import android.content.Context
import app.cash.sqldelight.async.coroutines.awaitAsOneOrNull
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import nl.ndat.tvlauncher.data.DatabaseContainer
import nl.ndat.tvlauncher.data.executeAsListFlow
import nl.ndat.tvlauncher.data.resolver.AppResolver
Expand All @@ -11,16 +14,18 @@ class AppRepository(
private val appResolver: AppResolver,
private val database: DatabaseContainer,
) {
private suspend fun commitApps(apps: Collection<App>) = database.transaction {
// Remove missing apps from database
val currentIds = apps.map { it.id }
database.apps.removeNotIn(currentIds)

// Upsert apps
apps.map { app -> commitApp(app) }
private suspend fun commitApps(apps: Collection<App>) = withContext(Dispatchers.IO) {
database.transaction {
// Remove missing apps from database
val currentIds = apps.map { it.id }
database.apps.removeNotIn(currentIds)

// Upsert apps
apps.map { app -> commitApp(app) }
}
}

private fun commitApp(app: App) {
private suspend fun commitApp(app: App) = withContext(Dispatchers.IO) {
database.apps.upsert(
displayName = app.displayName,
packageName = app.packageName,
Expand All @@ -30,12 +35,12 @@ class AppRepository(
)
}

suspend fun refreshAllApplications() {
suspend fun refreshAllApplications() = withContext(Dispatchers.IO) {
val apps = appResolver.getApplications(context)
commitApps(apps)
}

suspend fun refreshApplication(packageName: String) {
suspend fun refreshApplication(packageName: String) = withContext(Dispatchers.IO) {
val app = appResolver.getApplication(context, packageName)

if (app == null) database.apps.removeByPackageName(packageName)
Expand All @@ -44,9 +49,9 @@ class AppRepository(

fun getApps() = database.apps.getAll().executeAsListFlow()
fun getFavoriteApps() = database.apps.getAllFavorites(::App).executeAsListFlow()
suspend fun getByPackageName(packageName: String) = database.apps.getByPackageName(packageName).executeAsOneOrNull()
suspend fun getByPackageName(packageName: String) = withContext(Dispatchers.IO) { database.apps.getByPackageName(packageName).awaitAsOneOrNull() }

fun favorite(id: String) = database.apps.updateFavoriteAdd(id)
fun unfavorite(id: String) = database.apps.updateFavoriteRemove(id)
fun updateFavoriteOrder(id: String, order: Int) = database.apps.updateFavoriteOrder(id, order.toLong())
suspend fun favorite(id: String) = withContext(Dispatchers.IO) { database.apps.updateFavoriteAdd(id) }
suspend fun unfavorite(id: String) = withContext(Dispatchers.IO) { database.apps.updateFavoriteRemove(id) }
suspend fun updateFavoriteOrder(id: String, order: Int) = withContext(Dispatchers.IO) { database.apps.updateFavoriteOrder(id, order.toLong()) }
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package nl.ndat.tvlauncher.data.repository

import android.content.Context
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import nl.ndat.tvlauncher.data.DatabaseContainer
import nl.ndat.tvlauncher.data.executeAsListFlow
import nl.ndat.tvlauncher.data.model.ChannelType
Expand All @@ -13,7 +15,7 @@ class ChannelRepository(
private val channelResolver: ChannelResolver,
private val database: DatabaseContainer,
) {
private suspend fun commitChannels(type: ChannelType, channels: Collection<Channel>) =
private suspend fun commitChannels(type: ChannelType, channels: Collection<Channel>) = withContext(Dispatchers.IO) {
database.transaction {
// Remove missing channels from database
val currentIds = channels.map { it.id }
Expand All @@ -22,8 +24,10 @@ class ChannelRepository(
// Upsert channels
channels.map { channel -> commitChannel(channel) }
}
}


private fun commitChannel(channel: Channel) {
private suspend fun commitChannel(channel: Channel) = withContext(Dispatchers.IO) {
database.channels.upsert(
id = channel.id,
type = channel.type,
Expand All @@ -38,16 +42,18 @@ class ChannelRepository(
private suspend fun commitChannelPrograms(
channelId: String,
programs: Collection<ChannelProgram>,
) = database.transaction {
// Remove missing channels from database
val currentIds = programs.map { it.id }
database.channelPrograms.removeNotIn(channelId, currentIds)
) = withContext(Dispatchers.IO) {
database.transaction {
// Remove missing channels from database
val currentIds = programs.map { it.id }
database.channelPrograms.removeNotIn(channelId, currentIds)

// Upsert channels
programs.map { program -> commitChannelProgram(program) }
// Upsert channels
programs.map { program -> commitChannelProgram(program) }
}
}

private fun commitChannelProgram(program: ChannelProgram) {
private suspend fun commitChannelProgram(program: ChannelProgram) = withContext(Dispatchers.IO) {
database.channelPrograms.upsert(
id = program.id,
channelId = program.channelId,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package nl.ndat.tvlauncher.data.repository

import android.content.Context
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import nl.ndat.tvlauncher.data.DatabaseContainer
import nl.ndat.tvlauncher.data.executeAsListFlow
import nl.ndat.tvlauncher.data.resolver.InputResolver
Expand All @@ -11,16 +13,18 @@ class InputRepository(
private val inputResolver: InputResolver,
private val database: DatabaseContainer
) {
private suspend fun commitInputs(inputs: Collection<Input>) = database.transaction {
// Remove missing inputs from database
val currentIds = inputs.map { it.id }
database.inputs.removeNotIn(currentIds)
private suspend fun commitInputs(inputs: Collection<Input>) = withContext(Dispatchers.IO) {
database.transaction {
// Remove missing inputs from database
val currentIds = inputs.map { it.id }
database.inputs.removeNotIn(currentIds)

// Upsert inputs
inputs.map { input -> commitInput(input) }
// Upsert inputs
inputs.map { input -> commitInput(input) }
}
}

private fun commitInput(input: Input) {
private suspend fun commitInput(input: Input) = withContext(Dispatchers.IO) {
database.inputs.upsert(
id = input.id,
inputId = input.id,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ import androidx.tvprovider.media.tv.PreviewChannel
import androidx.tvprovider.media.tv.PreviewProgram
import androidx.tvprovider.media.tv.TvContractCompat
import androidx.tvprovider.media.tv.WatchNextProgram
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import nl.ndat.tvlauncher.data.model.ChannelProgramAspectRatio
import nl.ndat.tvlauncher.data.model.ChannelProgramInteractionType
import nl.ndat.tvlauncher.data.model.ChannelProgramType
Expand Down Expand Up @@ -50,16 +52,16 @@ class ChannelResolver {
null
}

fun getPreviewChannels(context: Context): List<Channel> {
suspend fun getPreviewChannels(context: Context): List<Channel> = withContext(Dispatchers.IO) {
val cursor = context.contentResolver.tryQuery(
TvContractCompat.Channels.CONTENT_URI,
PreviewChannel.Columns.PROJECTION,
) ?: return emptyList()
) ?: return@withContext emptyList()

return buildList {
buildList {
if (!cursor.moveToFirst()) {
Timber.w("Unable to move cursor")
return emptyList()
return@buildList
}

do {
Expand All @@ -86,16 +88,16 @@ class ChannelResolver {
}
}

fun getChannelPrograms(context: Context, channelId: Long): List<ChannelProgram> {
suspend fun getChannelPrograms(context: Context, channelId: Long): List<ChannelProgram> = withContext(Dispatchers.IO) {
val cursor = context.contentResolver.tryQuery(
TvContractCompat.buildPreviewProgramsUriForChannel(channelId),
PreviewProgram.PROJECTION,
) ?: return emptyList()
) ?: return@withContext emptyList()

return buildList {
buildList {
if (!cursor.moveToFirst()) {
Timber.w("Unable to move cursor")
return emptyList()
return@buildList
}

do {
Expand All @@ -112,16 +114,16 @@ class ChannelResolver {
}
}

fun getWatchNextPrograms(context: Context): List<ChannelProgram> {
suspend fun getWatchNextPrograms(context: Context): List<ChannelProgram> = withContext(Dispatchers.IO) {
val cursor = context.contentResolver.tryQuery(
TvContractCompat.WatchNextPrograms.CONTENT_URI,
WatchNextProgram.PROJECTION,
) ?: return emptyList()
) ?: return@withContext emptyList()

return buildList {
buildList {
if (!cursor.moveToFirst()) {
Timber.w("Unable to move cursor")
return emptyList()
return@buildList
}

do {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
import nl.ndat.tvlauncher.BuildConfig
import nl.ndat.tvlauncher.data.repository.AppRepository
import nl.ndat.tvlauncher.data.sqldelight.App
Expand All @@ -17,7 +18,7 @@ class AppsTabViewModel(
.map { apps -> apps.filterNot { app -> app.packageName == BuildConfig.APPLICATION_ID } }
.stateIn(viewModelScope, SharingStarted.Eagerly, emptyList())

fun favoriteApp(app: App, favorite: Boolean) {
fun favoriteApp(app: App, favorite: Boolean) = viewModelScope.launch {
if (favorite) appRepository.favorite(app.id)
else appRepository.unfavorite(app.id)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
import nl.ndat.tvlauncher.data.repository.AppRepository
import nl.ndat.tvlauncher.data.repository.ChannelRepository
import nl.ndat.tvlauncher.data.sqldelight.App
Expand All @@ -21,15 +22,15 @@ class HomeTabViewModel(

fun channelPrograms(channel: Channel) = channelRepository.getProgramsByChannel(channel)

fun favoriteApp(app: App, favorite: Boolean) {
fun favoriteApp(app: App, favorite: Boolean) = viewModelScope.launch {
// Return is state is already satisfied
if ((app.favoriteOrder != null) == favorite) return
if ((app.favoriteOrder != null) == favorite) return@launch

if (favorite) appRepository.favorite(app.id)
else appRepository.unfavorite(app.id)
}

fun setFavoriteOrder(app: App, order: Int) {
fun setFavoriteOrder(app: App, order: Int) = viewModelScope.launch {
// Make sure app is favorite first
if (app.favoriteOrder == null) appRepository.favorite(app.id)
appRepository.updateFavoriteOrder(app.id, order)
Expand Down

0 comments on commit 1f026a8

Please sign in to comment.