Update: Migrate to Paged FastAdapter

This commit is contained in:
machiav3lli 2022-01-13 01:43:40 +01:00
parent f6ff3844ae
commit edc0235119
7 changed files with 113 additions and 37 deletions

View File

@ -151,6 +151,7 @@ dependencies {
implementation("com.mikepenz:fastadapter:5.6.0") implementation("com.mikepenz:fastadapter:5.6.0")
implementation("com.mikepenz:fastadapter-extensions-diff:5.6.0") implementation("com.mikepenz:fastadapter-extensions-diff:5.6.0")
implementation("com.mikepenz:fastadapter-extensions-binding:5.6.0") implementation("com.mikepenz:fastadapter-extensions-binding:5.6.0")
implementation("com.mikepenz:fastadapter-extensions-paged:5.6.0")
// Coil // Coil
implementation 'io.coil-kt:coil:1.4.0' implementation 'io.coil-kt:coil:1.4.0'

View File

@ -2,6 +2,7 @@ package com.looker.droidify.database
import android.database.Cursor import android.database.Cursor
import android.os.CancellationSignal import android.os.CancellationSignal
import androidx.paging.DataSource
import androidx.room.* import androidx.room.*
import androidx.sqlite.db.SimpleSQLiteQuery import androidx.sqlite.db.SimpleSQLiteQuery
import androidx.sqlite.db.SupportSQLiteQuery import androidx.sqlite.db.SupportSQLiteQuery
@ -175,14 +176,13 @@ interface ProductDao : BaseDao<Product> {
@RawQuery(observedEntities = [Product::class]) @RawQuery(observedEntities = [Product::class])
fun queryList( fun queryList(
query: SupportSQLiteQuery query: SupportSQLiteQuery
): List<Product> ): DataSource.Factory<Int, Product>
// TODO optimize and simplify // TODO optimize and simplify
@Transaction
fun queryList( fun queryList(
installed: Boolean, updates: Boolean, searchQuery: String, installed: Boolean, updates: Boolean, searchQuery: String,
section: ProductItem.Section, order: ProductItem.Order, numberOfItems: Int = 0 section: ProductItem.Section, order: ProductItem.Order, numberOfItems: Int = 0
): List<Product> { ): DataSource.Factory<Int, Product> {
val builder = QueryBuilder() val builder = QueryBuilder()
val signatureMatches = """installed.${ROW_SIGNATURE} IS NOT NULL AND val signatureMatches = """installed.${ROW_SIGNATURE} IS NOT NULL AND

View File

@ -11,9 +11,13 @@ import com.looker.droidify.database.Product
import com.looker.droidify.databinding.FragmentExploreXBinding import com.looker.droidify.databinding.FragmentExploreXBinding
import com.looker.droidify.entity.Repository import com.looker.droidify.entity.Repository
import com.looker.droidify.ui.adapters.AppListAdapter import com.looker.droidify.ui.adapters.AppListAdapter
import com.looker.droidify.ui.items.VAppItem
import com.looker.droidify.ui.viewmodels.MainNavFragmentViewModelX import com.looker.droidify.ui.viewmodels.MainNavFragmentViewModelX
import com.looker.droidify.utility.PRODUCT_ASYNC_DIFFER_CONFIG
import com.looker.droidify.utility.RxUtils import com.looker.droidify.utility.RxUtils
import com.looker.droidify.utility.extension.resources.getDrawableCompat import com.looker.droidify.utility.extension.resources.getDrawableCompat
import com.mikepenz.fastadapter.FastAdapter
import com.mikepenz.fastadapter.paged.PagedModelAdapter
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
import io.reactivex.rxjava3.schedulers.Schedulers import io.reactivex.rxjava3.schedulers.Schedulers
import me.zhanghai.android.fastscroll.FastScrollerBuilder import me.zhanghai.android.fastscroll.FastScrollerBuilder
@ -23,6 +27,8 @@ class ExploreFragment : MainNavFragmentX() {
override lateinit var viewModel: MainNavFragmentViewModelX override lateinit var viewModel: MainNavFragmentViewModelX
private lateinit var binding: FragmentExploreXBinding private lateinit var binding: FragmentExploreXBinding
private lateinit var appsItemAdapter: PagedModelAdapter<Product, VAppItem>
private var appsFastAdapter: FastAdapter<VAppItem>? = null
override val source = Source.AVAILABLE override val source = Source.AVAILABLE
@ -40,13 +46,19 @@ class ExploreFragment : MainNavFragmentX() {
viewModel = ViewModelProvider(this, viewModelFactory) viewModel = ViewModelProvider(this, viewModelFactory)
.get(MainNavFragmentViewModelX::class.java) .get(MainNavFragmentViewModelX::class.java)
appsItemAdapter = PagedModelAdapter<Product, VAppItem>(PRODUCT_ASYNC_DIFFER_CONFIG) {
it.data_item?.let { item ->
VAppItem(item, repositories[it.repository_id])
}
}
appsFastAdapter = FastAdapter.with(appsItemAdapter)
appsFastAdapter?.setHasStableIds(true)
binding.recyclerView.apply { binding.recyclerView.apply {
layoutManager = LinearLayoutManager(context) layoutManager = LinearLayoutManager(requireContext())
isMotionEventSplittingEnabled = false
isVerticalScrollBarEnabled = false
setHasFixedSize(true) setHasFixedSize(true)
recycledViewPool.setMaxRecycledViews(AppListAdapter.ViewType.PRODUCT.ordinal, 30) recycledViewPool.setMaxRecycledViews(AppListAdapter.ViewType.PRODUCT.ordinal, 30)
adapter = AppListAdapter { mainActivityX.navigateProduct(it.packageName) } adapter = appsFastAdapter
FastScrollerBuilder(this) FastScrollerBuilder(this)
.useMd2Style() .useMd2Style()
.setThumbDrawable(this.context.getDrawableCompat(R.drawable.scrollbar_thumb)) .setThumbDrawable(this.context.getDrawableCompat(R.drawable.scrollbar_thumb))

View File

@ -8,16 +8,18 @@ import androidx.lifecycle.ViewModelProvider
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.looker.droidify.R import com.looker.droidify.R
import com.looker.droidify.database.Product
import com.looker.droidify.databinding.FragmentInstalledXBinding import com.looker.droidify.databinding.FragmentInstalledXBinding
import com.looker.droidify.entity.ProductItem
import com.looker.droidify.entity.Repository import com.looker.droidify.entity.Repository
import com.looker.droidify.ui.adapters.AppListAdapter
import com.looker.droidify.ui.items.HAppItem import com.looker.droidify.ui.items.HAppItem
import com.looker.droidify.ui.items.VAppItem import com.looker.droidify.ui.items.VAppItem
import com.looker.droidify.ui.viewmodels.MainNavFragmentViewModelX import com.looker.droidify.ui.viewmodels.MainNavFragmentViewModelX
import com.looker.droidify.utility.PRODUCT_ASYNC_DIFFER_CONFIG
import com.looker.droidify.utility.RxUtils import com.looker.droidify.utility.RxUtils
import com.looker.droidify.utility.extension.resources.getDrawableCompat import com.looker.droidify.utility.extension.resources.getDrawableCompat
import com.mikepenz.fastadapter.FastAdapter import com.mikepenz.fastadapter.FastAdapter
import com.mikepenz.fastadapter.adapters.ItemAdapter import com.mikepenz.fastadapter.paged.PagedModelAdapter
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
import io.reactivex.rxjava3.schedulers.Schedulers import io.reactivex.rxjava3.schedulers.Schedulers
import me.zhanghai.android.fastscroll.FastScrollerBuilder import me.zhanghai.android.fastscroll.FastScrollerBuilder
@ -27,9 +29,9 @@ class InstalledFragment : MainNavFragmentX() {
override lateinit var viewModel: MainNavFragmentViewModelX override lateinit var viewModel: MainNavFragmentViewModelX
private lateinit var binding: FragmentInstalledXBinding private lateinit var binding: FragmentInstalledXBinding
private val installedItemAdapter = ItemAdapter<VAppItem>() private lateinit var installedItemAdapter: PagedModelAdapter<Product, VAppItem>
private var installedFastAdapter: FastAdapter<VAppItem>? = null private var installedFastAdapter: FastAdapter<VAppItem>? = null
private val updatedItemAdapter = ItemAdapter<HAppItem>() private lateinit var updatedItemAdapter: PagedModelAdapter<Product, HAppItem>
private var updatedFastAdapter: FastAdapter<HAppItem>? = null private var updatedFastAdapter: FastAdapter<HAppItem>? = null
override val source = Source.INSTALLED override val source = Source.INSTALLED
@ -48,12 +50,23 @@ class InstalledFragment : MainNavFragmentX() {
viewModel = ViewModelProvider(this, viewModelFactory) viewModel = ViewModelProvider(this, viewModelFactory)
.get(MainNavFragmentViewModelX::class.java) .get(MainNavFragmentViewModelX::class.java)
installedItemAdapter = PagedModelAdapter<Product, VAppItem>(PRODUCT_ASYNC_DIFFER_CONFIG) {
it.data_item?.let { item ->
VAppItem(item, repositories[it.repository_id])
}
}
updatedItemAdapter = PagedModelAdapter<Product, HAppItem>(PRODUCT_ASYNC_DIFFER_CONFIG) {
it.data_item?.let { item ->
// TODO filter for only updated apps and add placeholder
HAppItem(item, repositories[it.repository_id])
}
}
installedFastAdapter = FastAdapter.with(installedItemAdapter) installedFastAdapter = FastAdapter.with(installedItemAdapter)
installedFastAdapter?.setHasStableIds(true) installedFastAdapter?.setHasStableIds(true)
binding.installedRecycler.apply { binding.installedRecycler.apply {
layoutManager = LinearLayoutManager(requireContext()) layoutManager = LinearLayoutManager(requireContext())
isMotionEventSplittingEnabled = false recycledViewPool.setMaxRecycledViews(AppListAdapter.ViewType.PRODUCT.ordinal, 30)
isVerticalScrollBarEnabled = false
adapter = installedFastAdapter adapter = installedFastAdapter
FastScrollerBuilder(this) FastScrollerBuilder(this)
.useMd2Style() .useMd2Style()
@ -64,13 +77,8 @@ class InstalledFragment : MainNavFragmentX() {
updatedFastAdapter?.setHasStableIds(true) updatedFastAdapter?.setHasStableIds(true)
binding.updatedRecycler.apply { binding.updatedRecycler.apply {
layoutManager = LinearLayoutManager(requireContext(), RecyclerView.HORIZONTAL, false) layoutManager = LinearLayoutManager(requireContext(), RecyclerView.HORIZONTAL, false)
isMotionEventSplittingEnabled = false recycledViewPool.setMaxRecycledViews(AppListAdapter.ViewType.PRODUCT.ordinal, 30)
isVerticalScrollBarEnabled = false
adapter = updatedFastAdapter adapter = updatedFastAdapter
FastScrollerBuilder(this)
.useMd2Style()
.setThumbDrawable(this.context.getDrawableCompat(R.drawable.scrollbar_thumb))
.build()
} }
return binding.root return binding.root
} }

View File

@ -8,16 +8,18 @@ import androidx.lifecycle.ViewModelProvider
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.looker.droidify.R import com.looker.droidify.R
import com.looker.droidify.database.Product
import com.looker.droidify.databinding.FragmentLatestXBinding import com.looker.droidify.databinding.FragmentLatestXBinding
import com.looker.droidify.entity.ProductItem
import com.looker.droidify.entity.Repository import com.looker.droidify.entity.Repository
import com.looker.droidify.ui.adapters.AppListAdapter
import com.looker.droidify.ui.items.HAppItem import com.looker.droidify.ui.items.HAppItem
import com.looker.droidify.ui.items.VAppItem import com.looker.droidify.ui.items.VAppItem
import com.looker.droidify.ui.viewmodels.MainNavFragmentViewModelX import com.looker.droidify.ui.viewmodels.MainNavFragmentViewModelX
import com.looker.droidify.utility.PRODUCT_ASYNC_DIFFER_CONFIG
import com.looker.droidify.utility.RxUtils import com.looker.droidify.utility.RxUtils
import com.looker.droidify.utility.extension.resources.getDrawableCompat import com.looker.droidify.utility.extension.resources.getDrawableCompat
import com.mikepenz.fastadapter.FastAdapter import com.mikepenz.fastadapter.FastAdapter
import com.mikepenz.fastadapter.adapters.ItemAdapter import com.mikepenz.fastadapter.paged.PagedModelAdapter
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
import io.reactivex.rxjava3.schedulers.Schedulers import io.reactivex.rxjava3.schedulers.Schedulers
import me.zhanghai.android.fastscroll.FastScrollerBuilder import me.zhanghai.android.fastscroll.FastScrollerBuilder
@ -27,11 +29,12 @@ class LatestFragment : MainNavFragmentX() {
override lateinit var viewModel: MainNavFragmentViewModelX override lateinit var viewModel: MainNavFragmentViewModelX
private lateinit var binding: FragmentLatestXBinding private lateinit var binding: FragmentLatestXBinding
private val updatedItemAdapter = ItemAdapter<VAppItem>() private lateinit var updatedItemAdapter: PagedModelAdapter<Product, VAppItem>
private var updatedFastAdapter: FastAdapter<VAppItem>? = null private var updatedFastAdapter: FastAdapter<VAppItem>? = null
private val newItemAdapter = ItemAdapter<HAppItem>() private lateinit var newItemAdapter: PagedModelAdapter<Product, HAppItem>
private var newFastAdapter: FastAdapter<HAppItem>? = null private var newFastAdapter: FastAdapter<HAppItem>? = null
// TODO replace the source with one that get a certain amount of updated apps
override val source = Source.AVAILABLE override val source = Source.AVAILABLE
private var repositories: Map<Long, Repository> = mapOf() private var repositories: Map<Long, Repository> = mapOf()
@ -48,12 +51,23 @@ class LatestFragment : MainNavFragmentX() {
viewModel = ViewModelProvider(this, viewModelFactory) viewModel = ViewModelProvider(this, viewModelFactory)
.get(MainNavFragmentViewModelX::class.java) .get(MainNavFragmentViewModelX::class.java)
updatedItemAdapter = PagedModelAdapter<Product, VAppItem>(PRODUCT_ASYNC_DIFFER_CONFIG) {
it.data_item?.let { item ->
VAppItem(item, repositories[it.repository_id])
}
}
newItemAdapter = PagedModelAdapter<Product, HAppItem>(PRODUCT_ASYNC_DIFFER_CONFIG) {
it.data_item?.let { item ->
// TODO filter for only new apps and add placeholder
HAppItem(item, repositories[it.repository_id])
}
}
updatedFastAdapter = FastAdapter.with(updatedItemAdapter) updatedFastAdapter = FastAdapter.with(updatedItemAdapter)
updatedFastAdapter?.setHasStableIds(true) updatedFastAdapter?.setHasStableIds(true)
binding.updatedRecycler.apply { binding.updatedRecycler.apply {
layoutManager = LinearLayoutManager(requireContext()) layoutManager = LinearLayoutManager(requireContext())
isMotionEventSplittingEnabled = false recycledViewPool.setMaxRecycledViews(AppListAdapter.ViewType.PRODUCT.ordinal, 30)
isVerticalScrollBarEnabled = false
adapter = updatedFastAdapter adapter = updatedFastAdapter
FastScrollerBuilder(this) FastScrollerBuilder(this)
.useMd2Style() .useMd2Style()
@ -64,13 +78,8 @@ class LatestFragment : MainNavFragmentX() {
newFastAdapter?.setHasStableIds(true) newFastAdapter?.setHasStableIds(true)
binding.newRecycler.apply { binding.newRecycler.apply {
layoutManager = LinearLayoutManager(requireContext(), RecyclerView.HORIZONTAL, false) layoutManager = LinearLayoutManager(requireContext(), RecyclerView.HORIZONTAL, false)
isMotionEventSplittingEnabled = false recycledViewPool.setMaxRecycledViews(AppListAdapter.ViewType.PRODUCT.ordinal, 30)
isVerticalScrollBarEnabled = false
adapter = newFastAdapter adapter = newFastAdapter
FastScrollerBuilder(this)
.useMd2Style()
.setThumbDrawable(this.context.getDrawableCompat(R.drawable.scrollbar_thumb))
.build()
} }
return binding.root return binding.root
} }

View File

@ -1,14 +1,18 @@
package com.looker.droidify.ui.viewmodels package com.looker.droidify.ui.viewmodels
import androidx.lifecycle.MediatorLiveData import androidx.lifecycle.LiveData
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import androidx.paging.DataSource
import androidx.paging.LivePagedListBuilder
import androidx.paging.PagedList
import com.looker.droidify.database.DatabaseX import com.looker.droidify.database.DatabaseX
import com.looker.droidify.database.Product import com.looker.droidify.database.Product
import com.looker.droidify.entity.ProductItem import com.looker.droidify.entity.ProductItem
import com.looker.droidify.ui.fragments.Request import com.looker.droidify.ui.fragments.Request
import com.looker.droidify.ui.fragments.Source import com.looker.droidify.ui.fragments.Source
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.SharingStarted
@ -68,15 +72,36 @@ class MainNavFragmentViewModelX(val db: DatabaseX, source: Source) : ViewModel()
} }
} }
var productsList = MediatorLiveData<MutableList<Product>>() var productsList: LiveData<PagedList<Product>>
init {
val pagedListConfig = PagedList.Config.Builder()
.setPageSize(30)
.setPrefetchDistance(30)
.setEnablePlaceholders(false)
.build()
val request = request(source)
productsList = LivePagedListBuilder(
db.productDao.queryList(
installed = request.installed,
updates = request.updates,
searchQuery = request.searchQuery,
section = request.section,
order = request.order,
numberOfItems = request.numberOfItems
), pagedListConfig
).build()
}
fun fillList(source: Source) { fun fillList(source: Source) {
viewModelScope.launch { CoroutineScope(Dispatchers.Default).launch {
productsList.value = query(request(source))?.toMutableList() // productsList = query(request(source))
} }
} }
private suspend fun query(request: Request): List<Product>? { private suspend fun query(request: Request): DataSource.Factory<Int, Product> {
return withContext(Dispatchers.Default) { return withContext(Dispatchers.Default) {
db.productDao db.productDao
.queryList( .queryList(

View File

@ -10,6 +10,8 @@ import android.content.res.Configuration
import android.database.Cursor import android.database.Cursor
import android.graphics.drawable.Drawable import android.graphics.drawable.Drawable
import android.os.Build import android.os.Build
import androidx.recyclerview.widget.AsyncDifferConfig
import androidx.recyclerview.widget.DiffUtil
import com.fasterxml.jackson.core.JsonGenerator import com.fasterxml.jackson.core.JsonGenerator
import com.fasterxml.jackson.core.JsonParser import com.fasterxml.jackson.core.JsonParser
import com.looker.droidify.* import com.looker.droidify.*
@ -235,3 +237,22 @@ fun jsonGenerate(callback: (JsonGenerator) -> Unit): ByteArray {
Json.factory.createGenerator(outputStream).use { it.writeDictionary(callback) } Json.factory.createGenerator(outputStream).use { it.writeDictionary(callback) }
return outputStream.toByteArray() return outputStream.toByteArray()
} }
val PRODUCT_ASYNC_DIFFER_CONFIG
get() = AsyncDifferConfig.Builder(object :
DiffUtil.ItemCallback<com.looker.droidify.database.Product>() {
override fun areItemsTheSame(
oldItem: com.looker.droidify.database.Product,
newItem: com.looker.droidify.database.Product
): Boolean {
return oldItem.repository_id == newItem.repository_id
&& oldItem.package_name == newItem.package_name
}
override fun areContentsTheSame(
oldItem: com.looker.droidify.database.Product,
newItem: com.looker.droidify.database.Product
): Boolean {
return oldItem.data_item == newItem.data_item
}
}).build()