From 98b25b69fb0e04938a257ea193dcd6436c32d6e3 Mon Sep 17 00:00:00 2001 From: Mihai-Cristian Condrea Date: Mon, 11 Aug 2025 18:54:58 +0300 Subject: [PATCH 1/2] Add cancellable app data loading job --- .../apps/manager/ui/AppManagerViewModel.kt | 136 +++++++++--------- 1 file changed, 71 insertions(+), 65 deletions(-) diff --git a/app/src/main/kotlin/com/d4rk/cleaner/app/apps/manager/ui/AppManagerViewModel.kt b/app/src/main/kotlin/com/d4rk/cleaner/app/apps/manager/ui/AppManagerViewModel.kt index 5e2910b6..b160f395 100644 --- a/app/src/main/kotlin/com/d4rk/cleaner/app/apps/manager/ui/AppManagerViewModel.kt +++ b/app/src/main/kotlin/com/d4rk/cleaner/app/apps/manager/ui/AppManagerViewModel.kt @@ -5,6 +5,7 @@ import android.content.BroadcastReceiver import android.content.Context import android.content.Intent import android.content.IntentFilter +import androidx.lifecycle.viewModelScope import com.d4rk.android.libs.apptoolkit.core.di.DispatcherProvider import com.d4rk.android.libs.apptoolkit.core.domain.model.network.DataState import com.d4rk.android.libs.apptoolkit.core.domain.model.ui.UiSnackbar @@ -28,10 +29,12 @@ import com.d4rk.cleaner.app.apps.manager.domain.usecases.ShareApkUseCase import com.d4rk.cleaner.app.apps.manager.domain.usecases.ShareAppUseCase import com.d4rk.cleaner.app.apps.manager.domain.usecases.UninstallAppUseCase import com.d4rk.cleaner.core.utils.helpers.CleaningEventBus +import kotlinx.coroutines.Job import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.flowOn +import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch @@ -61,6 +64,7 @@ class AppManagerViewModel( private var pendingUninstallPackage: String? = null private var pendingInstallPackage: String? = null + private var loadJob: Job? = null fun onSearchQueryChange(query: String) { _searchQuery.value = query @@ -150,90 +154,92 @@ class AppManagerViewModel( } private fun loadAppData() { - launch { - val installedAppsFlow = - getInstalledAppsUseCase().flowOn(dispatchers.default).onEach { result -> - _uiState.update { currentState -> - when (result) { - is DataState.Loading -> currentState.copy( - data = currentState.data?.copy( - userAppsLoading = true, - systemAppsLoading = true - ) + loadJob?.cancel() + + val installedAppsFlow = + getInstalledAppsUseCase().flowOn(dispatchers.default).onEach { result -> + _uiState.update { currentState -> + when (result) { + is DataState.Loading -> currentState.copy( + data = currentState.data?.copy( + userAppsLoading = true, + systemAppsLoading = true ) - - is DataState.Success -> { - val pm = applicationContext.packageManager - val comparator = compareBy( - { runCatching { pm.getApplicationLabel(it).toString() } - .getOrNull() - ?.lowercase(Locale.getDefault()) - ?: it.packageName.lowercase(Locale.getDefault()) }, - { it.packageName.lowercase(Locale.getDefault()) } - ) - currentState.copy( - data = currentState.data?.copy( - installedApps = result.data.sortedWith(comparator), - userAppsLoading = false, - systemAppsLoading = false - ) - ) - } - - is DataState.Error -> currentState.copy( + ), + + is DataState.Success -> { + val pm = applicationContext.packageManager + val comparator = compareBy( + { runCatching { pm.getApplicationLabel(it).toString() } + .getOrNull() + ?.lowercase(Locale.getDefault()) + ?: it.packageName.lowercase(Locale.getDefault()) }, + { it.packageName.lowercase(Locale.getDefault()) } + ) + currentState.copy( data = currentState.data?.copy( + installedApps = result.data.sortedWith(comparator), userAppsLoading = false, systemAppsLoading = false ) ) - } + }, + + is DataState.Error -> currentState.copy( + data = currentState.data?.copy( + userAppsLoading = false, + systemAppsLoading = false + ) + ) } } + } - val apkFilesFlow = - getApkFilesFromStorageUseCase().flowOn(dispatchers.default).onEach { result -> - _uiState.update { currentState -> - when (result) { - is DataState.Loading -> currentState.copy( - data = currentState.data?.copy( - apkFilesLoading = true - ) + val apkFilesFlow = + getApkFilesFromStorageUseCase().flowOn(dispatchers.default).onEach { result -> + _uiState.update { currentState -> + when (result) { + is DataState.Loading -> currentState.copy( + data = currentState.data?.copy( + apkFilesLoading = true ) - - is DataState.Success -> currentState.copy( - data = currentState.data?.copy( - apkFiles = result.data.sortedBy { - it.path.substringAfterLast('/').lowercase() - }, - apkFilesLoading = false)) - - is DataState.Error -> currentState.copy( - data = currentState.data?.copy( - apkFilesLoading = false - ) + ), + + is DataState.Success -> currentState.copy( + data = currentState.data?.copy( + apkFiles = result.data.sortedBy { + it.path.substringAfterLast('/').lowercase() + }, + apkFilesLoading = false)), + + is DataState.Error -> currentState.copy( + data = currentState.data?.copy( + apkFilesLoading = false ) - } + ) } } + } - val usageStatsFlow = - getAppsLastUsedUseCase().flowOn(dispatchers.default).onEach { result -> - _uiState.update { currentState -> - when (result) { - is DataState.Success -> currentState.copy( - data = currentState.data?.copy( - appUsageStats = result.data - ) + val usageStatsFlow = + getAppsLastUsedUseCase().flowOn(dispatchers.default).onEach { result -> + _uiState.update { currentState -> + when (result) { + is DataState.Success -> currentState.copy( + data = currentState.data?.copy( + appUsageStats = result.data ) + ), - else -> currentState - } + else -> currentState } } + } - launch(dispatchers.io) { installedAppsFlow.collectLatest {} } - launch(dispatchers.io) { apkFilesFlow.collectLatest {} } - launch(dispatchers.io) { usageStatsFlow.collectLatest {} } + loadJob = viewModelScope.launch(dispatchers.io) { + installedAppsFlow.launchIn(this) + apkFilesFlow.launchIn(this) + usageStatsFlow.launchIn(this) } } From fba221680e9424185dbeade5e882f8812245d523 Mon Sep 17 00:00:00 2001 From: Mihai-Cristian Condrea Date: Mon, 11 Aug 2025 19:21:01 +0300 Subject: [PATCH 2/2] Fix when expressions in app data loader --- .../app/apps/manager/ui/AppManagerViewModel.kt | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/app/src/main/kotlin/com/d4rk/cleaner/app/apps/manager/ui/AppManagerViewModel.kt b/app/src/main/kotlin/com/d4rk/cleaner/app/apps/manager/ui/AppManagerViewModel.kt index b160f395..36cae788 100644 --- a/app/src/main/kotlin/com/d4rk/cleaner/app/apps/manager/ui/AppManagerViewModel.kt +++ b/app/src/main/kotlin/com/d4rk/cleaner/app/apps/manager/ui/AppManagerViewModel.kt @@ -165,7 +165,7 @@ class AppManagerViewModel( userAppsLoading = true, systemAppsLoading = true ) - ), + ) is DataState.Success -> { val pm = applicationContext.packageManager @@ -183,7 +183,7 @@ class AppManagerViewModel( systemAppsLoading = false ) ) - }, + } is DataState.Error -> currentState.copy( data = currentState.data?.copy( @@ -203,14 +203,16 @@ class AppManagerViewModel( data = currentState.data?.copy( apkFilesLoading = true ) - ), + ) is DataState.Success -> currentState.copy( data = currentState.data?.copy( apkFiles = result.data.sortedBy { it.path.substringAfterLast('/').lowercase() }, - apkFilesLoading = false)), + apkFilesLoading = false + ) + ) is DataState.Error -> currentState.copy( data = currentState.data?.copy( @@ -229,7 +231,7 @@ class AppManagerViewModel( data = currentState.data?.copy( appUsageStats = result.data ) - ), + ) else -> currentState }