diff --git a/app/src/main/java/foundation/e/apps/data/application/ApplicationRepository.kt b/app/src/main/java/foundation/e/apps/data/application/ApplicationRepository.kt index 96ebe5f78e6335491e69637f9ed87646e928b4e9..a10684bf87753d5b9d03b8fdb4a7d01ad700dc4a 100644 --- a/app/src/main/java/foundation/e/apps/data/application/ApplicationRepository.kt +++ b/app/src/main/java/foundation/e/apps/data/application/ApplicationRepository.kt @@ -35,8 +35,11 @@ import foundation.e.apps.data.enums.Source import foundation.e.apps.data.enums.Status import foundation.e.apps.data.handleNetworkResult import foundation.e.apps.data.install.models.AppInstall +import kotlinx.coroutines.CancellationException import kotlinx.coroutines.async -import kotlinx.coroutines.coroutineScope +import kotlinx.coroutines.currentCoroutineContext +import kotlinx.coroutines.isActive +import kotlinx.coroutines.supervisorScope import javax.inject.Inject import javax.inject.Singleton @@ -76,7 +79,7 @@ class ApplicationRepository @Inject constructor( ) } - coroutineScope { + supervisorScope { val resultsBySource = mutableMapOf>>() val sources = stores.getStores().keys val deferredResults = sources.associateWith { source -> @@ -84,7 +87,14 @@ class ApplicationRepository @Inject constructor( } sources.forEach { source -> - val result = deferredResults.getValue(source).await() + val result = try { + deferredResults.getValue(source).await() + } catch (exception: CancellationException) { + if (!currentCoroutineContext().isActive) { + throw exception + } + createSourceFailureResult(source, exception) + } resultsBySource[source] = result emitResult(result, resultsBySource) } @@ -123,6 +133,23 @@ class ApplicationRepository @Inject constructor( } } + private fun createSourceFailureResult( + source: Source, + exception: Exception, + ): ResultSupreme> { + val status = ResultStatus.UNKNOWN.apply { + message = exception.message.orEmpty() + } + + setHomeErrorMessage(status, source) + return ResultSupreme.create( + status = status, + data = emptyList(), + message = status.message, + exception = exception, + ) + } + private fun mergeResults(results: Collection>>): List { val merged = results.flatMap { it.data.orEmpty() }.toMutableList() merged.sortBy { diff --git a/app/src/test/java/foundation/e/apps/data/application/ApplicationRepositoryHomeTest.kt b/app/src/test/java/foundation/e/apps/data/application/ApplicationRepositoryHomeTest.kt index e1ad4b2acbcd93fd4d13f9dbcdb7f7d32aab9096..ff9f436d58e80ad8ae2e3dcd95567338b658eebc 100644 --- a/app/src/test/java/foundation/e/apps/data/application/ApplicationRepositoryHomeTest.kt +++ b/app/src/test/java/foundation/e/apps/data/application/ApplicationRepositoryHomeTest.kt @@ -45,6 +45,7 @@ import io.mockk.coVerify import io.mockk.every import io.mockk.mockk import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.CancellationException import kotlinx.coroutines.flow.take import kotlinx.coroutines.flow.toList import kotlinx.coroutines.test.runTest @@ -191,6 +192,33 @@ class ApplicationRepositoryHomeTest { .containsExactly(ApplicationRepository.APP_TYPE_ANY) } + @Test + fun fetchHomeScreenDataKeepsOtherSourcesWhenOneSourceIsCancelled() = runTest { + val cancelledStore = object : StoreRepository { + override suspend fun getHomeScreenData(list: MutableList): List { + throw CancellationException("request cancelled") + } + override suspend fun getAppDetails(packageName: String) = Application() + override suspend fun getSearchResults(pattern: String) = emptyList() + override suspend fun getSearchSuggestions(pattern: String) = emptyList() + } + val repositories = linkedMapOf( + Source.PLAY_STORE to fakeStore(playStoreHome), + Source.OPEN_SOURCE to cancelledStore, + ) + every { stores.getStores() } returns repositories + every { stores.getStore(Source.PLAY_STORE) } returns repositories.getValue(Source.PLAY_STORE) + every { stores.getStore(Source.OPEN_SOURCE) } returns cancelledStore + + val emissions = applicationRepository.getHomeScreenData().asFlow().take(2).toList() + + val result = emissions.last() + assertThat(result.isUnknownError()).isTrue() + assertThat(result.message).contains("request cancelled") + assertThat(result.data?.map { it.source }) + .containsExactly(ApplicationRepository.APP_TYPE_ANY) + } + @Test fun getSelectedAppTypesReturnsEnabledStores() { every { stores.isStoreEnabled(Source.PLAY_STORE) } returns true