Refactor: Enhance AppDao query capabilities
This commit refactors the `AppDao` to provide more flexible and robust querying options for application data. Key changes: - **Renamed `_rawQueryAppEntities` to `_rawStreamAppEntities`**: This clarifies the function's purpose of returning a Flow of entities. - **Added `_rawQueryAppEntities`**: A new suspend function that directly returns a List of entities, for non-streaming use cases. - **Introduced `query` function**: A new public suspend function that mirrors the functionality of `stream` but returns a `List<AppEntity>` instead of a `Flow`. - **Enhanced `searchQuery` private function**: - Now accepts lists for `categoriesToInclude`, `categoriesToExclude`, `antiFeaturesToInclude`, and `antiFeaturesToExclude` to allow filtering by multiple criteria. - Uses `DISTINCT` in the SQL query to avoid duplicate app entries. - Corrected join condition for `category_app_relation` from `app.id = category_app_relation.appId` to `app.id = category_app_relation.id`. - Corrected table name for anti-features from `anti_feature_app_relation` to `anti_features_app_relation`. - Improved SQL query construction for category and anti-feature filtering using `IN` and `NOT IN` clauses. - Ensured `ORDER BY` clause is always present, even if `searchQuery` is null. - Prefixed table names in `ORDER BY` clause (e.g., `app.lastUpdated`) for clarity and to avoid ambiguity. - **Updated `stream` function**: Now utilizes the refactored `searchQuery` function and passes through all new filtering parameters. - **Updated database schema**: - Changed `onDelete` action for the foreign key in the `authentication` table to `CASCADE`. - **Updated Room tests**: - Simplified setup by removing legacy database initialization. - Added tests for new sorting and category filtering functionalities in `AppDao`.
This commit is contained in:
parent
c943c2a149
commit
ae2bdaea19
@ -2,7 +2,7 @@
|
|||||||
"formatVersion": 1,
|
"formatVersion": 1,
|
||||||
"database": {
|
"database": {
|
||||||
"version": 1,
|
"version": 1,
|
||||||
"identityHash": "099d07ab258fe12cb0a660545bd36e63",
|
"identityHash": "e64dac21017b34894f6d9d7890184178",
|
||||||
"entities": [
|
"entities": [
|
||||||
{
|
{
|
||||||
"tableName": "anti_feature",
|
"tableName": "anti_feature",
|
||||||
@ -142,7 +142,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"tableName": "authentication",
|
"tableName": "authentication",
|
||||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`password` TEXT NOT NULL, `username` TEXT NOT NULL, `initializationVector` TEXT NOT NULL, `repoId` INTEGER NOT NULL, PRIMARY KEY(`repoId`), FOREIGN KEY(`repoId`) REFERENCES `repository`(`id`) ON UPDATE NO ACTION ON DELETE NO ACTION )",
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`password` TEXT NOT NULL, `username` TEXT NOT NULL, `initializationVector` TEXT NOT NULL, `repoId` INTEGER NOT NULL, PRIMARY KEY(`repoId`), FOREIGN KEY(`repoId`) REFERENCES `repository`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
|
||||||
"fields": [
|
"fields": [
|
||||||
{
|
{
|
||||||
"fieldPath": "password",
|
"fieldPath": "password",
|
||||||
@ -179,7 +179,7 @@
|
|||||||
"foreignKeys": [
|
"foreignKeys": [
|
||||||
{
|
{
|
||||||
"table": "repository",
|
"table": "repository",
|
||||||
"onDelete": "NO ACTION",
|
"onDelete": "CASCADE",
|
||||||
"onUpdate": "NO ACTION",
|
"onUpdate": "NO ACTION",
|
||||||
"columns": [
|
"columns": [
|
||||||
"repoId"
|
"repoId"
|
||||||
@ -1078,7 +1078,7 @@
|
|||||||
"views": [],
|
"views": [],
|
||||||
"setupQueries": [
|
"setupQueries": [
|
||||||
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
|
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
|
||||||
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '099d07ab258fe12cb0a660545bd36e63')"
|
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'e64dac21017b34894f6d9d7890184178')"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -3,32 +3,25 @@ package com.looker.droidify
|
|||||||
import android.content.Context
|
import android.content.Context
|
||||||
import com.looker.droidify.data.local.dao.AppDao
|
import com.looker.droidify.data.local.dao.AppDao
|
||||||
import com.looker.droidify.data.local.dao.IndexDao
|
import com.looker.droidify.data.local.dao.IndexDao
|
||||||
import com.looker.droidify.database.Database
|
import com.looker.droidify.datastore.model.SortOrder
|
||||||
import com.looker.droidify.domain.model.Fingerprint
|
import com.looker.droidify.domain.model.Fingerprint
|
||||||
import com.looker.droidify.domain.model.Repo
|
import com.looker.droidify.domain.model.Repo
|
||||||
import com.looker.droidify.domain.model.VersionInfo
|
import com.looker.droidify.domain.model.VersionInfo
|
||||||
import com.looker.droidify.index.RepositoryUpdater
|
|
||||||
import com.looker.droidify.index.RepositoryUpdater.IndexType
|
|
||||||
import com.looker.droidify.model.Repository
|
import com.looker.droidify.model.Repository
|
||||||
import com.looker.droidify.sync.FakeDownloader
|
import com.looker.droidify.sync.FakeDownloader
|
||||||
import com.looker.droidify.sync.common.JsonParser
|
import com.looker.droidify.sync.common.JsonParser
|
||||||
import com.looker.droidify.sync.common.assets
|
|
||||||
import com.looker.droidify.sync.common.benchmark
|
|
||||||
import com.looker.droidify.sync.common.downloadIndex
|
import com.looker.droidify.sync.common.downloadIndex
|
||||||
import com.looker.droidify.sync.v2.model.IndexV2
|
import com.looker.droidify.sync.v2.model.IndexV2
|
||||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||||
import dagger.hilt.android.testing.HiltAndroidRule
|
import dagger.hilt.android.testing.HiltAndroidRule
|
||||||
import dagger.hilt.android.testing.HiltAndroidTest
|
import dagger.hilt.android.testing.HiltAndroidTest
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.Dispatchers
|
|
||||||
import kotlinx.coroutines.flow.first
|
|
||||||
import kotlinx.coroutines.test.runTest
|
import kotlinx.coroutines.test.runTest
|
||||||
import org.junit.Before
|
import org.junit.Before
|
||||||
import org.junit.Rule
|
import org.junit.Rule
|
||||||
import java.io.File
|
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import kotlin.system.measureTimeMillis
|
|
||||||
import kotlin.test.Test
|
import kotlin.test.Test
|
||||||
|
import kotlin.test.assertTrue
|
||||||
|
|
||||||
@HiltAndroidTest
|
@HiltAndroidTest
|
||||||
class RoomTesting {
|
class RoomTesting {
|
||||||
@ -53,98 +46,60 @@ class RoomTesting {
|
|||||||
@Before
|
@Before
|
||||||
fun before() = runTest {
|
fun before() = runTest {
|
||||||
hiltRule.inject()
|
hiltRule.inject()
|
||||||
setupLegacy()
|
launch {
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun roomBenchmark() = runTest {
|
|
||||||
val izzy = izzyLegacy.toRepo(1)
|
val izzy = izzyLegacy.toRepo(1)
|
||||||
val insert = benchmark(1) {
|
val izzyFile = FakeDownloader.downloadIndex(context, izzy, "i2", "index-v2.json")
|
||||||
val v2File = FakeDownloader
|
val izzyIndex =
|
||||||
.downloadIndex(context, izzy, "izzy-v2", "index-v2.json")
|
JsonParser.decodeFromString<IndexV2>(izzyFile.readBytes().decodeToString())
|
||||||
measureTimeMillis {
|
|
||||||
val index = JsonParser.decodeFromString<IndexV2>(
|
|
||||||
v2File.readBytes().decodeToString(),
|
|
||||||
)
|
|
||||||
indexDao.insertIndex(
|
indexDao.insertIndex(
|
||||||
fingerprint = izzy.fingerprint!!,
|
fingerprint = izzy.fingerprint!!,
|
||||||
index = index,
|
index = izzyIndex,
|
||||||
expectedRepoId = izzy.id,
|
expectedRepoId = izzy.id,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
// launch {
|
||||||
val fdroid = fdroidLegacy.toRepo(2)
|
// val fdroid = fdroidLegacy.toRepo(2)
|
||||||
val insertFDroid = benchmark(1) {
|
// val fdroidFile =
|
||||||
val v2File = FakeDownloader
|
// FakeDownloader.downloadIndex(context, fdroid, "f2", "fdroid-index-v2.json")
|
||||||
.downloadIndex(context, fdroid, "fdroid-v2", "fdroid-index-v2.json")
|
// val fdroidIndex =
|
||||||
measureTimeMillis {
|
// JsonParser.decodeFromString<IndexV2>(fdroidFile.readBytes().decodeToString())
|
||||||
val index = JsonParser.decodeFromString<IndexV2>(
|
// indexDao.insertIndex(
|
||||||
v2File.readBytes().decodeToString(),
|
// fingerprint = fdroid.fingerprint!!,
|
||||||
)
|
// index = fdroidIndex,
|
||||||
indexDao.insertIndex(
|
// expectedRepoId = fdroid.id,
|
||||||
fingerprint = fdroid.fingerprint!!,
|
// )
|
||||||
index = index,
|
// }
|
||||||
expectedRepoId = fdroid.id,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
val query = appDao.queryAppEntity("com.looker.droidify")
|
|
||||||
println(query.first().joinToString("\n"))
|
|
||||||
println(insert)
|
|
||||||
println(insertFDroid)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun legacyBenchmark() {
|
fun sortOrderTest() = runTest {
|
||||||
val insert = benchmark(1) {
|
val lastUpdatedQuery = appDao.query(sortOrder = SortOrder.UPDATED)
|
||||||
val createFile = File.createTempFile("index", "entry")
|
var previousUpdated = Long.MAX_VALUE
|
||||||
val mergerFile = File.createTempFile("index", "merger")
|
lastUpdatedQuery.forEach {
|
||||||
val jarStream = assets("izzy_index_v1.jar")
|
println("Previous: $previousUpdated, Current: ${it.lastUpdated}")
|
||||||
jarStream.copyTo(createFile.outputStream())
|
assertTrue(it.lastUpdated <= previousUpdated)
|
||||||
measureTimeMillis {
|
previousUpdated = it.lastUpdated
|
||||||
RepositoryUpdater.processFile(
|
|
||||||
context = context,
|
|
||||||
repository = izzyLegacy,
|
|
||||||
indexType = IndexType.INDEX_V1,
|
|
||||||
unstable = false,
|
|
||||||
file = createFile,
|
|
||||||
mergerFile = mergerFile,
|
|
||||||
lastModified = "",
|
|
||||||
entityTag = "",
|
|
||||||
callback = { _, _, _ -> },
|
|
||||||
)
|
|
||||||
createFile.delete()
|
|
||||||
mergerFile.delete()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
val insertFDroid = benchmark(1) {
|
|
||||||
val createFile = File.createTempFile("index", "entry")
|
|
||||||
val mergerFile = File.createTempFile("index", "merger")
|
|
||||||
val jarStream = assets("fdroid_index_v1.jar")
|
|
||||||
jarStream.copyTo(createFile.outputStream())
|
|
||||||
measureTimeMillis {
|
|
||||||
RepositoryUpdater.processFile(
|
|
||||||
context = context,
|
|
||||||
repository = fdroidLegacy,
|
|
||||||
indexType = IndexType.INDEX_V1,
|
|
||||||
unstable = false,
|
|
||||||
file = createFile,
|
|
||||||
mergerFile = mergerFile,
|
|
||||||
lastModified = "",
|
|
||||||
entityTag = "",
|
|
||||||
callback = { _, _, _ -> },
|
|
||||||
)
|
|
||||||
createFile.delete()
|
|
||||||
mergerFile.delete()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
println(insert)
|
|
||||||
println(insertFDroid)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun setupLegacy() {
|
val addedQuery = appDao.query(sortOrder = SortOrder.ADDED)
|
||||||
Database.init(context)
|
var previousAdded = Long.MAX_VALUE
|
||||||
RepositoryUpdater.init(CoroutineScope(Dispatchers.Default), FakeDownloader)
|
addedQuery.forEach {
|
||||||
|
println("Previous: $previousAdded, Current: ${it.added}")
|
||||||
|
assertTrue(it.added <= previousAdded)
|
||||||
|
previousAdded = it.added
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun categoryTest() = runTest {
|
||||||
|
val categoryQuery = appDao.query(
|
||||||
|
sortOrder = SortOrder.UPDATED,
|
||||||
|
categoriesToInclude = listOf("Games", "Food"),
|
||||||
|
)
|
||||||
|
val nonCategoryQuery = appDao.query(
|
||||||
|
sortOrder = SortOrder.UPDATED,
|
||||||
|
categoriesToExclude = listOf("Games", "Food"),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -18,67 +18,129 @@ import kotlinx.coroutines.flow.Flow
|
|||||||
@Dao
|
@Dao
|
||||||
interface AppDao {
|
interface AppDao {
|
||||||
|
|
||||||
@RawQuery(observedEntities = [
|
@RawQuery(
|
||||||
|
observedEntities = [
|
||||||
AppEntity::class,
|
AppEntity::class,
|
||||||
VersionEntity::class,
|
VersionEntity::class,
|
||||||
CategoryAppRelation::class,
|
CategoryAppRelation::class,
|
||||||
AntiFeatureAppRelation::class,
|
AntiFeatureAppRelation::class,
|
||||||
])
|
],
|
||||||
fun _rawQueryAppEntities(query: SimpleSQLiteQuery): Flow<List<AppEntity>>
|
)
|
||||||
|
fun _rawStreamAppEntities(query: SimpleSQLiteQuery): Flow<List<AppEntity>>
|
||||||
|
|
||||||
|
@RawQuery
|
||||||
|
suspend fun _rawQueryAppEntities(query: SimpleSQLiteQuery): List<AppEntity>
|
||||||
|
|
||||||
/**
|
|
||||||
* @param category: get by default name and use the boolean to ignore the app category
|
|
||||||
* @param antiFeature: get by tag and use the boolean to ignore the anti feature
|
|
||||||
*
|
|
||||||
* Add `canUpdate` and `isInstalled` is the next layer
|
|
||||||
* */
|
|
||||||
fun stream(
|
fun stream(
|
||||||
|
sortOrder: SortOrder,
|
||||||
|
searchQuery: String? = null,
|
||||||
|
repoId: Int? = null,
|
||||||
|
categoriesToInclude: List<DefaultName>? = null,
|
||||||
|
categoriesToExclude: List<DefaultName>? = null,
|
||||||
|
antiFeaturesToInclude: List<Tag>? = null,
|
||||||
|
antiFeaturesToExclude: List<Tag>? = null,
|
||||||
|
): Flow<List<AppEntity>> = _rawStreamAppEntities(
|
||||||
|
searchQuery(
|
||||||
|
sortOrder = sortOrder,
|
||||||
|
searchQuery = searchQuery,
|
||||||
|
repoId = repoId,
|
||||||
|
categoriesToInclude = categoriesToInclude,
|
||||||
|
categoriesToExclude = categoriesToExclude,
|
||||||
|
antiFeaturesToInclude = antiFeaturesToInclude,
|
||||||
|
antiFeaturesToExclude = antiFeaturesToExclude,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
suspend fun query(
|
||||||
|
sortOrder: SortOrder,
|
||||||
|
searchQuery: String? = null,
|
||||||
|
repoId: Int? = null,
|
||||||
|
categoriesToInclude: List<DefaultName>? = null,
|
||||||
|
categoriesToExclude: List<DefaultName>? = null,
|
||||||
|
antiFeaturesToInclude: List<Tag>? = null,
|
||||||
|
antiFeaturesToExclude: List<Tag>? = null,
|
||||||
|
): List<AppEntity> = _rawQueryAppEntities(
|
||||||
|
searchQuery(
|
||||||
|
sortOrder = sortOrder,
|
||||||
|
searchQuery = searchQuery,
|
||||||
|
repoId = repoId,
|
||||||
|
categoriesToInclude = categoriesToInclude,
|
||||||
|
categoriesToExclude = categoriesToExclude,
|
||||||
|
antiFeaturesToInclude = antiFeaturesToInclude,
|
||||||
|
antiFeaturesToExclude = antiFeaturesToExclude,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
private fun searchQuery(
|
||||||
sortOrder: SortOrder,
|
sortOrder: SortOrder,
|
||||||
searchQuery: String?,
|
searchQuery: String?,
|
||||||
repoId: Int?,
|
repoId: Int?,
|
||||||
category: Pair<DefaultName, Boolean>?,
|
categoriesToInclude: List<DefaultName>?,
|
||||||
antiFeature: Pair<Tag, Boolean>?,
|
categoriesToExclude: List<DefaultName>?,
|
||||||
): Flow<List<AppEntity>> {
|
antiFeaturesToInclude: List<Tag>?,
|
||||||
|
antiFeaturesToExclude: List<Tag>?,
|
||||||
|
): SimpleSQLiteQuery {
|
||||||
val args = arrayListOf<Any?>()
|
val args = arrayListOf<Any?>()
|
||||||
|
|
||||||
val query = buildString(1024) {
|
val query = buildString(1024) {
|
||||||
append("SELECT app.* FROM app")
|
append("SELECT DISTINCT app.* FROM app")
|
||||||
append(" LEFT JOIN version ON app.id = version.appId")
|
append(" LEFT JOIN version ON app.id = version.appId")
|
||||||
append(" LEFT JOIN category_app_relation ON app.id = category_app_relation.appId")
|
append(" LEFT JOIN category_app_relation ON app.id = category_app_relation.id")
|
||||||
append(" LEFT JOIN anti_feature_app_relation ON app.id = anti_feature_app_relation.appId")
|
append(" LEFT JOIN anti_features_app_relation ON app.id = anti_features_app_relation.appId")
|
||||||
append(" WHERE 1")
|
append(" WHERE 1")
|
||||||
|
|
||||||
if (repoId != null) {
|
if (repoId != null) {
|
||||||
append(" AND app.repoId = ?")
|
append(" AND app.repoId = ?")
|
||||||
args.add(repoId)
|
args.add(repoId)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (category != null) {
|
if (categoriesToInclude != null) {
|
||||||
val (name, hide) = category
|
append(" AND category_app_relation.defaultName IN (")
|
||||||
if (!hide) {
|
append(categoriesToInclude.joinToString(", ") { "?" })
|
||||||
append(" AND category_app_relation.defaultName = ?")
|
append(")")
|
||||||
} else {
|
args.addAll(categoriesToInclude)
|
||||||
append(" AND category_app_relation.defaultName != ?")
|
|
||||||
}
|
|
||||||
args.add(name)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (antiFeature != null) {
|
if (categoriesToExclude != null) {
|
||||||
val (tag, hide) = antiFeature
|
append(" AND category_app_relation.defaultName NOT IN (")
|
||||||
if (!hide) {
|
append(categoriesToExclude.joinToString(", ") { "?" })
|
||||||
append(" AND anti_feature_app_relation.tag = ?")
|
append(")")
|
||||||
} else {
|
args.addAll(categoriesToExclude)
|
||||||
append(" AND anti_feature_app_relation.tag != ?")
|
|
||||||
}
|
}
|
||||||
args.add(tag)
|
|
||||||
|
if (antiFeaturesToInclude != null) {
|
||||||
|
append(" AND anti_features_app_relation.tag IN (")
|
||||||
|
append(antiFeaturesToInclude.joinToString(", ") { "?" })
|
||||||
|
append(")")
|
||||||
|
args.addAll(antiFeaturesToInclude)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (antiFeaturesToExclude != null) {
|
||||||
|
append(" AND anti_features_app_relation.tag NOT IN (")
|
||||||
|
append(antiFeaturesToExclude.joinToString(", ") { "?" })
|
||||||
|
append(")")
|
||||||
|
args.addAll(antiFeaturesToExclude)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (searchQuery != null) {
|
if (searchQuery != null) {
|
||||||
val searchPattern = "%${searchQuery}%"
|
val searchPattern = "%${searchQuery}%"
|
||||||
append(" AND (app.name LIKE ? OR app.summary LIKE ? OR app.packageName LIKE ? OR app.description LIKE ?)")
|
append(
|
||||||
|
"""
|
||||||
|
AND (
|
||||||
|
app.name LIKE ?
|
||||||
|
OR app.summary LIKE ?
|
||||||
|
OR app.packageName LIKE ?
|
||||||
|
OR app.description LIKE ?
|
||||||
|
)""",
|
||||||
|
)
|
||||||
args.addAll(listOf(searchPattern, searchPattern, searchPattern, searchPattern))
|
args.addAll(listOf(searchPattern, searchPattern, searchPattern, searchPattern))
|
||||||
|
}
|
||||||
|
|
||||||
append(" ORDER BY ")
|
append(" ORDER BY ")
|
||||||
|
|
||||||
// Weighting: name > summary > packageName > description
|
// Weighting: name > summary > packageName > description
|
||||||
|
if (searchQuery != null) {
|
||||||
|
val searchPattern = "%${searchQuery}%"
|
||||||
append("(CASE WHEN app.name LIKE ? THEN 4 ELSE 0 END) + ")
|
append("(CASE WHEN app.name LIKE ? THEN 4 ELSE 0 END) + ")
|
||||||
append("(CASE WHEN app.summary LIKE ? THEN 3 ELSE 0 END) + ")
|
append("(CASE WHEN app.summary LIKE ? THEN 3 ELSE 0 END) + ")
|
||||||
append("(CASE WHEN app.packageName LIKE ? THEN 2 ELSE 0 END) + ")
|
append("(CASE WHEN app.packageName LIKE ? THEN 2 ELSE 0 END) + ")
|
||||||
@ -86,12 +148,8 @@ interface AppDao {
|
|||||||
args.addAll(listOf(searchPattern, searchPattern, searchPattern, searchPattern))
|
args.addAll(listOf(searchPattern, searchPattern, searchPattern, searchPattern))
|
||||||
}
|
}
|
||||||
|
|
||||||
if (searchQuery == null) {
|
|
||||||
append(" ORDER BY ")
|
|
||||||
}
|
|
||||||
|
|
||||||
when (sortOrder) {
|
when (sortOrder) {
|
||||||
SortOrder.UPDATED -> append("app,lastUpdated DESC, ")
|
SortOrder.UPDATED -> append("app.lastUpdated DESC, ")
|
||||||
SortOrder.ADDED -> append("app.added DESC, ")
|
SortOrder.ADDED -> append("app.added DESC, ")
|
||||||
SortOrder.SIZE -> append("version.apk_size DESC, ")
|
SortOrder.SIZE -> append("version.apk_size DESC, ")
|
||||||
SortOrder.NAME -> Unit
|
SortOrder.NAME -> Unit
|
||||||
@ -99,7 +157,7 @@ interface AppDao {
|
|||||||
append("app.name COLLATE LOCALIZED ASC")
|
append("app.name COLLATE LOCALIZED ASC")
|
||||||
}
|
}
|
||||||
|
|
||||||
return _rawQueryAppEntities(SimpleSQLiteQuery(query, args.toTypedArray()))
|
return SimpleSQLiteQuery(query, args.toTypedArray())
|
||||||
}
|
}
|
||||||
|
|
||||||
@Query(
|
@Query(
|
||||||
|
Loading…
x
Reference in New Issue
Block a user