installed: improve sluggish app load
The VM was calling PackageUtil.getAllValidPackages(context) eagerly in its constructor, which Hilt creates on the main thread. The PackageManager IPC plus a per-app loadLabel() for sorting froze the screen long enough that the existing ContainedLoadingIndicator never got a chance to render.
This commit is contained in:
@@ -33,6 +33,9 @@ import com.aurora.store.util.PackageUtil
|
|||||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
import kotlinx.coroutines.CoroutineStart
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.async
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.asStateFlow
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||||
@@ -46,30 +49,44 @@ class InstalledViewModel @Inject constructor(
|
|||||||
private val webAppDetailsHelper: WebAppDetailsHelper
|
private val webAppDetailsHelper: WebAppDetailsHelper
|
||||||
) : ViewModel() {
|
) : ViewModel() {
|
||||||
|
|
||||||
private val packages = PackageUtil.getAllValidPackages(context)
|
|
||||||
private val blacklist = blacklistProvider.blacklist
|
private val blacklist = blacklistProvider.blacklist
|
||||||
|
|
||||||
private val _apps = MutableStateFlow<PagingData<App>>(PagingData.empty())
|
private val _apps = MutableStateFlow<PagingData<App>>(PagingData.empty())
|
||||||
val apps = _apps.asStateFlow()
|
val apps = _apps.asStateFlow()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enumerating installed packages calls into PackageManager and resolves a label
|
||||||
|
* per app for sorting. Done lazily on [Dispatchers.IO] so the VM constructor and
|
||||||
|
* the main thread stay snappy; paging awaits this before fetching the first page.
|
||||||
|
*/
|
||||||
|
private val pagedPackages = viewModelScope.async(
|
||||||
|
context = Dispatchers.IO,
|
||||||
|
start = CoroutineStart.LAZY
|
||||||
|
) {
|
||||||
|
PackageUtil.getAllValidPackages(context)
|
||||||
|
.filterNot { it.packageName in blacklist }
|
||||||
|
.chunked(PAGE_SIZE)
|
||||||
|
}
|
||||||
|
|
||||||
init {
|
init {
|
||||||
fetchApps()
|
fetchApps()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun fetchApps() {
|
private fun fetchApps() {
|
||||||
val pagedPackages = packages
|
manualPager(pageSize = PAGE_SIZE) { page ->
|
||||||
.filterNot { it.packageName in blacklist }
|
|
||||||
.chunked(20)
|
|
||||||
|
|
||||||
manualPager { page ->
|
|
||||||
// page is 1-indexed, but list is 0-indexed
|
// page is 1-indexed, but list is 0-indexed
|
||||||
val chunk = pagedPackages.getOrNull(page - 1)
|
val chunks = pagedPackages.await()
|
||||||
|
val chunk = chunks.getOrNull(page - 1)
|
||||||
?: return@manualPager PageResult(emptyList<App>(), hasMore = false)
|
?: return@manualPager PageResult(emptyList<App>(), hasMore = false)
|
||||||
val items = webAppDetailsHelper.getAppDetails(chunk.map { it.packageName })
|
val items = webAppDetailsHelper.getAppDetails(chunk.map { it.packageName })
|
||||||
PageResult(items, hasMore = page < pagedPackages.size)
|
PageResult(items, hasMore = page < chunks.size)
|
||||||
}.flow.distinctUntilChanged()
|
}.flow.distinctUntilChanged()
|
||||||
.cachedIn(viewModelScope)
|
.cachedIn(viewModelScope)
|
||||||
.onEach { _apps.value = it }
|
.onEach { _apps.value = it }
|
||||||
.launchIn(viewModelScope)
|
.launchIn(viewModelScope)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val PAGE_SIZE = 20
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user