Update: Replace CursorOwner's de-/attaching with VM's fillList

This commit is contained in:
machiav3lli 2022-01-03 23:36:04 +01:00
parent bedf5cad3f
commit 3082e7153d
7 changed files with 178 additions and 58 deletions

View File

@ -171,6 +171,97 @@ interface ProductDao : BaseDao<Product> {
return query(SimpleSQLiteQuery(builder.build()))
}
@RawQuery
fun queryList(
query: SupportSQLiteQuery
): List<Product>
// TODO optimize and simplify
@Transaction
fun queryList(
installed: Boolean, updates: Boolean, searchQuery: String,
section: ProductItem.Section, order: ProductItem.Order
): List<Product> {
val builder = QueryBuilder()
val signatureMatches = """installed.${ROW_SIGNATURE} IS NOT NULL AND
product.${ROW_SIGNATURES} LIKE ('%.' || installed.${ROW_SIGNATURE} || '.%') AND
product.${ROW_SIGNATURES} != ''"""
builder += """SELECT product.rowid AS _id, product.${ROW_REPOSITORY_ID},
product.${ROW_PACKAGE_NAME}, product.${ROW_NAME},
product.${ROW_SUMMARY}, installed.${ROW_VERSION},
(COALESCE(lock.${ROW_VERSION_CODE}, -1) NOT IN (0, product.${ROW_VERSION_CODE}) AND
product.${ROW_COMPATIBLE} != 0 AND product.${ROW_VERSION_CODE} >
COALESCE(installed.${ROW_VERSION_CODE}, 0xffffffff) AND $signatureMatches)
AS ${ROW_CAN_UPDATE}, product.${ROW_COMPATIBLE},
product.${ROW_DATA_ITEM},"""
if (searchQuery.isNotEmpty()) {
builder += """(((product.${ROW_NAME} LIKE ? OR
product.${ROW_SUMMARY} LIKE ?) * 7) |
((product.${ROW_PACKAGE_NAME} LIKE ?) * 3) |
(product.${ROW_DESCRIPTION} LIKE ?)) AS ${ROW_MATCH_RANK},"""
builder %= List(4) { "%$searchQuery%" }
} else {
builder += "0 AS ${ROW_MATCH_RANK},"
}
builder += """MAX((product.${ROW_COMPATIBLE} AND
(installed.${ROW_SIGNATURE} IS NULL OR $signatureMatches)) ||
PRINTF('%016X', product.${ROW_VERSION_CODE})) FROM $ROW_PRODUCT_NAME AS product"""
builder += """JOIN $ROW_REPOSITORY_NAME AS repository
ON product.${ROW_REPOSITORY_ID} = repository.${ROW_ID}"""
builder += """LEFT JOIN $ROW_LOCK_NAME AS lock
ON product.${ROW_PACKAGE_NAME} = lock.${ROW_PACKAGE_NAME}"""
if (!installed && !updates) {
builder += "LEFT"
}
builder += """JOIN $ROW_INSTALLED_NAME AS installed
ON product.${ROW_PACKAGE_NAME} = installed.${ROW_PACKAGE_NAME}"""
if (section is ProductItem.Section.Category) {
builder += """JOIN $ROW_CATEGORY_NAME AS category
ON product.${ROW_PACKAGE_NAME} = category.${ROW_PACKAGE_NAME}"""
}
builder += """WHERE repository.${ROW_ENABLED} != 0 AND
repository.${ROW_DELETED} == 0"""
if (section is ProductItem.Section.Category) {
builder += "AND category.${ROW_NAME} = ?"
builder %= section.name
} else if (section is ProductItem.Section.Repository) {
builder += "AND product.${ROW_REPOSITORY_ID} = ?"
builder %= section.id.toString()
}
if (searchQuery.isNotEmpty()) {
builder += """AND $ROW_MATCH_RANK > 0"""
}
builder += "GROUP BY product.${ROW_PACKAGE_NAME} HAVING 1"
if (updates) {
builder += "AND $ROW_CAN_UPDATE"
}
builder += "ORDER BY"
if (searchQuery.isNotEmpty()) {
builder += """$ROW_MATCH_RANK DESC,"""
}
when (order) {
ProductItem.Order.NAME -> Unit
ProductItem.Order.DATE_ADDED -> builder += "product.${ROW_ADDED} DESC,"
ProductItem.Order.LAST_UPDATE -> builder += "product.${ROW_UPDATED} DESC,"
}::class
builder += "product.${ROW_NAME} COLLATE LOCALIZED ASC"
return queryList(SimpleSQLiteQuery(builder.build()))
}
}
@Dao

View File

@ -234,35 +234,6 @@ class MainActivityX : AppCompatActivity(), LoaderManager.LoaderCallbacks<Cursor>
syncConnection.binder?.setUpdateNotificationBlocker(blockerFragment)
}
fun attachCursorOwner(callback: CursorOwner.Callback, request: CursorOwner.Request) {
val oldActiveRequest = viewModel.activeRequests[request.id]
if (oldActiveRequest?.callback != null &&
oldActiveRequest.callback != callback && oldActiveRequest.cursor != null
) {
oldActiveRequest.callback.onCursorData(oldActiveRequest.request, null)
}
val cursor = if (oldActiveRequest?.request == request && oldActiveRequest.cursor != null) {
callback.onCursorData(request, oldActiveRequest.cursor)
oldActiveRequest.cursor
} else {
null
}
viewModel.activeRequests[request.id] = CursorOwner.ActiveRequest(request, callback, cursor)
if (cursor == null) {
LoaderManager.getInstance(this).restartLoader(request.id, null, this)
}
}
fun detachCursorOwner(callback: CursorOwner.Callback) {
for (id in viewModel.activeRequests.keys) {
val activeRequest = viewModel.activeRequests[id]!!
if (activeRequest.callback == callback) {
viewModel.activeRequests[id] = activeRequest.copy(callback = null)
}
}
}
override fun onCreateLoader(id: Int, args: Bundle?): Loader<Cursor> {
val request = viewModel.activeRequests[id]!!.request
return QueryLoader(this) {

View File

@ -59,7 +59,7 @@ class ExploreFragment : MainNavFragmentX(), CursorOwner.Callback {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
mainActivityX.attachCursorOwner(this, viewModel.request(source))
viewModel.fillList(source)
viewModel.db.repositoryDao.allFlowable
.observeOn(Schedulers.io())
.flatMapSingle { list -> RxUtils.querySingle { list.mapNotNull { it.trueData } } }
@ -68,12 +68,6 @@ class ExploreFragment : MainNavFragmentX(), CursorOwner.Callback {
.subscribe { repositories = it }
}
override fun onDestroyView() {
super.onDestroyView()
mainActivityX.detachCursorOwner(this)
}
override fun onCursorData(request: CursorOwner.Request, cursor: Cursor?) {
(binding.recyclerView.adapter as? AppListAdapter)?.apply {
this.cursor = cursor

View File

@ -72,7 +72,7 @@ class InstalledFragment : MainNavFragmentX(), CursorOwner.Callback {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
mainActivityX.attachCursorOwner(this, viewModel.request(source))
viewModel.fillList(source)
viewModel.db.repositoryDao.allFlowable
.observeOn(Schedulers.io())
.flatMapSingle { list -> RxUtils.querySingle { list.mapNotNull { it.trueData } } }
@ -81,12 +81,6 @@ class InstalledFragment : MainNavFragmentX(), CursorOwner.Callback {
.subscribe { repositories = it }
}
override fun onDestroyView() {
super.onDestroyView()
mainActivityX.detachCursorOwner(this)
}
override fun onCursorData(request: CursorOwner.Request, cursor: Cursor?) {
// TODO get a list instead of the cursor
// TODO use LiveData and observers instead of listeners

View File

@ -72,7 +72,7 @@ class LatestFragment : MainNavFragmentX(), CursorOwner.Callback {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
mainActivityX.attachCursorOwner(this, viewModel.request(source))
viewModel.fillList(source)
viewModel.db.repositoryDao.allFlowable
.observeOn(Schedulers.io())
.flatMapSingle { list -> RxUtils.querySingle { list.mapNotNull { it.trueData } } }
@ -81,12 +81,6 @@ class LatestFragment : MainNavFragmentX(), CursorOwner.Callback {
.subscribe { repositories = it }
}
override fun onDestroyView() {
super.onDestroyView()
mainActivityX.detachCursorOwner(this)
}
override fun onCursorData(request: CursorOwner.Request, cursor: Cursor?) {
// TODO get a list instead of the cursor
// TODO use LiveData and observers instead of listeners

View File

@ -18,7 +18,7 @@ abstract class MainNavFragmentX : Fragment(), CursorOwner.Callback {
internal fun setSearchQuery(searchQuery: String) {
viewModel.setSearchQuery(searchQuery) {
if (view != null) {
mainActivityX.attachCursorOwner(this, viewModel.request(source))
viewModel.fillList(source)
}
}
}
@ -26,7 +26,7 @@ abstract class MainNavFragmentX : Fragment(), CursorOwner.Callback {
internal fun setSection(section: ProductItem.Section) {
viewModel.setSection(section) {
if (view != null) {
mainActivityX.attachCursorOwner(this, viewModel.request(source))
viewModel.fillList(source)
}
}
}
@ -34,7 +34,7 @@ abstract class MainNavFragmentX : Fragment(), CursorOwner.Callback {
internal fun setOrder(order: ProductItem.Order) {
viewModel.setOrder(order) {
if (view != null) {
mainActivityX.attachCursorOwner(this, viewModel.request(source))
viewModel.fillList(source)
}
}
}
@ -44,4 +44,37 @@ enum class Source(val titleResId: Int, val sections: Boolean, val order: Boolean
AVAILABLE(R.string.available, true, true),
INSTALLED(R.string.installed, false, true),
UPDATES(R.string.updates, false, false)
}
sealed class Request {
internal abstract val id: Int
data class ProductsAvailable(
val searchQuery: String, val section: ProductItem.Section,
val order: ProductItem.Order,
) : Request() {
override val id: Int
get() = 1
}
data class ProductsInstalled(
val searchQuery: String, val section: ProductItem.Section,
val order: ProductItem.Order,
) : Request() {
override val id: Int
get() = 2
}
data class ProductsUpdates(
val searchQuery: String, val section: ProductItem.Section,
val order: ProductItem.Order,
) : Request() {
override val id: Int
get() = 3
}
object Repositories : Request() {
override val id: Int
get() = 4
}
}

View File

@ -1,11 +1,13 @@
package com.looker.droidify.ui.viewmodels
import androidx.lifecycle.MediatorLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.viewModelScope
import com.looker.droidify.database.CursorOwner
import com.looker.droidify.database.DatabaseX
import com.looker.droidify.database.Product
import com.looker.droidify.entity.ProductItem
import com.looker.droidify.ui.fragments.Request
import com.looker.droidify.ui.fragments.Source
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow
@ -13,6 +15,7 @@ import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
class MainNavFragmentViewModelX(val db: DatabaseX) : ViewModel() {
@ -37,7 +40,7 @@ class MainNavFragmentViewModelX(val db: DatabaseX) : ViewModel() {
started = SharingStarted.WhileSubscribed(5000)
)
fun request(source: Source): CursorOwner.Request {
fun request(source: Source): Request {
var mSearchQuery = ""
var mSections: ProductItem.Section = ProductItem.Section.All
var mOrder: ProductItem.Order = ProductItem.Order.NAME
@ -47,17 +50,17 @@ class MainNavFragmentViewModelX(val db: DatabaseX) : ViewModel() {
launch { order.collect { if (source.order) mOrder = it } }
}
return when (source) {
Source.AVAILABLE -> CursorOwner.Request.ProductsAvailable(
Source.AVAILABLE -> Request.ProductsAvailable(
mSearchQuery,
mSections,
mOrder
)
Source.INSTALLED -> CursorOwner.Request.ProductsInstalled(
Source.INSTALLED -> Request.ProductsInstalled(
mSearchQuery,
mSections,
mOrder
)
Source.UPDATES -> CursorOwner.Request.ProductsUpdates(
Source.UPDATES -> Request.ProductsUpdates(
mSearchQuery,
mSections,
mOrder
@ -65,6 +68,46 @@ class MainNavFragmentViewModelX(val db: DatabaseX) : ViewModel() {
}
}
var productsList = MediatorLiveData<MutableList<Product>>()
fun fillList(source: Source) {
viewModelScope.launch {
productsList.value = query(request(source))?.toMutableList()
}
}
private suspend fun query(request: Request): List<Product>? {
return withContext(Dispatchers.IO) {
when (request) {
is Request.ProductsAvailable -> db.productDao
.queryList(
installed = false,
updates = false,
searchQuery = request.searchQuery,
section = request.section,
order = request.order
)
is Request.ProductsInstalled -> db.productDao
.queryList(
installed = true,
updates = false,
searchQuery = request.searchQuery,
section = request.section,
order = request.order
)
is Request.ProductsUpdates -> db.productDao
.queryList(
installed = true,
updates = true,
searchQuery = request.searchQuery,
section = request.section,
order = request.order
)
else -> listOf()
}
}
}
fun setSection(newSection: ProductItem.Section, perform: () -> Unit) {
viewModelScope.launch {
if (newSection != sections.value) {