-
Couldn't load subscription status.
- Fork 133
[Woo POS][Local Catalog] Refactor and fix pull-to-refresh logic in products and variations #14816
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: trunk
Are you sure you want to change the base?
Changes from all commits
57f4048
a656b2f
5e911ae
4df733c
9e08a48
801786d
a7fd7db
1e0e98c
eb180f2
22bb958
e238460
8c895ce
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -13,11 +13,15 @@ import com.woocommerce.android.ui.woopos.common.data.models.WooPosProductModel | |
| import com.woocommerce.android.ui.woopos.common.data.models.WooPosProductModelMapper | ||
| import com.woocommerce.android.ui.woopos.common.data.models.WooPosWCProductToWooPosProductModelMapper | ||
| import com.woocommerce.android.ui.woopos.common.data.toWooPosVariation | ||
| import com.woocommerce.android.ui.woopos.home.items.products.WooPosProductsDataSource.VariationsResult | ||
| import com.woocommerce.android.ui.woopos.home.items.variations.WooPosVariationsLRUCache | ||
| import com.woocommerce.android.ui.woopos.localcatalog.PosLocalCatalogSyncResult | ||
| import com.woocommerce.android.ui.woopos.localcatalog.ProductsResult | ||
| import com.woocommerce.android.ui.woopos.localcatalog.VariationsResult | ||
| import com.woocommerce.android.ui.woopos.localcatalog.WooPosFullSyncRequirement | ||
| import com.woocommerce.android.ui.woopos.localcatalog.WooPosFullSyncStatusChecker | ||
| import com.woocommerce.android.ui.woopos.localcatalog.WooPosLocalCatalogSyncRepository | ||
| import com.woocommerce.android.ui.woopos.localcatalog.WooPosPerformInstantCatalogFullSync | ||
| import com.woocommerce.android.ui.woopos.localcatalog.WooPosSyncResult | ||
| import com.woocommerce.android.util.WooLog | ||
| import kotlinx.coroutines.Dispatchers | ||
| import kotlinx.coroutines.async | ||
|
|
@@ -27,6 +31,7 @@ import kotlinx.coroutines.flow.first | |
| import kotlinx.coroutines.flow.firstOrNull | ||
| import kotlinx.coroutines.flow.flow | ||
| import kotlinx.coroutines.flow.flowOn | ||
| import kotlinx.coroutines.flow.last | ||
| import kotlinx.coroutines.flow.map | ||
| import kotlinx.coroutines.flow.take | ||
| import kotlinx.coroutines.withContext | ||
|
|
@@ -127,6 +132,14 @@ class WooPosProductsDataSource @Inject constructor( | |
| activeSource?.canLoadMoreVariations(numOfVariations) | ||
| ?: error("canLoadMoreVariations - Data source not selected") | ||
|
|
||
| suspend fun refreshProducts(): Result<WooPosSyncResult> = | ||
| activeSource?.refreshProducts() | ||
| ?: error("refreshProducts - Data source not selected") | ||
|
|
||
| suspend fun refreshVariations(productId: Long): Result<WooPosSyncResult> = | ||
| activeSource?.refreshVariations(productId) | ||
| ?: error("refreshVariations - Data source not selected") | ||
|
|
||
| suspend fun getVariationById( | ||
| productId: Long, | ||
| variationId: Long | ||
|
|
@@ -135,16 +148,6 @@ class WooPosProductsDataSource @Inject constructor( | |
| ?: error("GetVariationById - Data source not selected") | ||
| } | ||
|
|
||
| sealed class ProductsResult { | ||
| data class Cached(val products: List<WooPosProductModel>) : ProductsResult() | ||
| data class Remote(val productsResult: Result<List<WooPosProductModel>>) : ProductsResult() | ||
| } | ||
|
|
||
| sealed class VariationsResult { | ||
| data class Cached(val data: List<WooPosVariation>) : VariationsResult() | ||
| data class Remote(val result: Result<List<WooPosVariation>>) : VariationsResult() | ||
| } | ||
|
|
||
| sealed class WooPosPrepopulatingDataStatus { | ||
| data object Syncing : WooPosPrepopulatingDataStatus() | ||
| data object Completed : WooPosPrepopulatingDataStatus() | ||
|
|
@@ -158,6 +161,7 @@ class WooPosProductsInDbDataSource @Inject constructor( | |
| private val productMapper: WooPosProductModelMapper, | ||
| private val variationMapper: WooPosVariationMapper, | ||
| private val performInstantCatalogFullSync: WooPosPerformInstantCatalogFullSync, | ||
| private val localCatalogSyncRepository: WooPosLocalCatalogSyncRepository, | ||
| ) : WooPosProductsDataSourceInterface { | ||
|
|
||
| private fun getProductsFromDatabaseFlow(): Flow<List<WooPosProductModel>> { | ||
|
|
@@ -174,9 +178,9 @@ class WooPosProductsInDbDataSource @Inject constructor( | |
|
|
||
| override fun fetchFirstProductsPage( | ||
| forceRefresh: Boolean | ||
| ): Flow<WooPosProductsDataSource.ProductsResult> = getProductsFromDatabaseFlow() | ||
| ): Flow<ProductsResult> = getProductsFromDatabaseFlow() | ||
| .map { products -> | ||
| WooPosProductsDataSource.ProductsResult.Remote(Result.success(products)) | ||
| ProductsResult.Remote(Result.success(products)) | ||
| } | ||
| .flowOn(Dispatchers.IO) | ||
|
|
||
|
|
@@ -232,6 +236,24 @@ class WooPosProductsInDbDataSource @Inject constructor( | |
| ) | ||
| return result.getOrNull()?.toWooPosVariation(variationMapper) | ||
| } | ||
|
|
||
| override suspend fun refreshProducts(): Result<WooPosSyncResult> = withContext(Dispatchers.IO) { | ||
| selectedSite.getOrNull()?.let { site -> | ||
| val syncResult = localCatalogSyncRepository.syncLocalCatalogIncremental(site) | ||
| Result.success(syncResult) | ||
| } ?: Result.success( | ||
| PosLocalCatalogSyncResult.Failure.UnexpectedError("No site selected") | ||
| ) | ||
| } | ||
|
|
||
| override suspend fun refreshVariations(productId: Long): Result<WooPosSyncResult> = withContext(Dispatchers.IO) { | ||
| selectedSite.getOrNull()?.let { site -> | ||
| val syncResult = localCatalogSyncRepository.syncLocalCatalogIncremental(site) | ||
| Result.success(syncResult) | ||
| } ?: Result.success( | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same as above. |
||
| PosLocalCatalogSyncResult.Failure.UnexpectedError("No site selected") | ||
| ) | ||
| } | ||
| } | ||
|
|
||
| class WooPosProductsRemoteDataSource @Inject constructor( | ||
|
|
@@ -297,22 +319,22 @@ class WooPosProductsRemoteDataSource @Inject constructor( | |
|
|
||
| override fun fetchFirstProductsPage( | ||
| forceRefresh: Boolean | ||
| ): Flow<WooPosProductsDataSource.ProductsResult> = flow { | ||
| ): Flow<ProductsResult> = flow { | ||
| offset.set(0) | ||
| productsIndex.clearCache() | ||
|
|
||
| if (!forceRefresh) { | ||
| val cachedProducts = sortProducts(productsCache.getAll()).take(NORMAL_PAGE_SIZE) | ||
| emit(WooPosProductsDataSource.ProductsResult.Cached(cachedProducts)) | ||
| emit(ProductsResult.Cached(cachedProducts)) | ||
| } | ||
|
|
||
| val fetchResult = fetchProducts() | ||
|
|
||
| if (fetchResult.isSuccess) { | ||
| emit(WooPosProductsDataSource.ProductsResult.Remote(Result.success(fetchResult.getOrThrow()))) | ||
| emit(ProductsResult.Remote(Result.success(fetchResult.getOrThrow()))) | ||
| } else { | ||
| emit( | ||
| WooPosProductsDataSource.ProductsResult.Remote( | ||
| ProductsResult.Remote( | ||
| Result.failure( | ||
| fetchResult.exceptionOrNull() ?: Exception("Unknown error") | ||
| ) | ||
|
|
@@ -488,6 +510,24 @@ class WooPosProductsRemoteDataSource @Inject constructor( | |
| ?.toWooPosVariation(variationMapper) | ||
| } | ||
|
|
||
| override suspend fun refreshProducts(): Result<WooPosSyncResult> = withContext( | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
I can't fine the convo right now, but Andrei mentioned we have not been wiping cache on PTR - I remember we used to do it early in the POS but then switched to keeping the in-memory cache until app restart. Do you know at which point we started calling There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I just found out that the only place the in-memory cache is cleared is in There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ahh, good find! So this PR is ready to be tested, right? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, ready 👍 |
||
| Dispatchers.IO | ||
| ) { | ||
| // We can return the last-emitted result which will always be the instance of [ProductsResult.Remote] because | ||
| // when forceRefresh = true the in-memory cache gets wiped out and the flow fetches from remote only. | ||
| val productsResult = fetchFirstProductsPage(forceRefresh = true).last() | ||
samiuelson marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| Result.success(productsResult) | ||
| } | ||
|
|
||
| override suspend fun refreshVariations( | ||
| productId: Long | ||
| ): Result<WooPosSyncResult> = withContext(Dispatchers.IO) { | ||
| // We can return the last-emitted result which will always be the instance of [VariationsResult.Remote] because | ||
samiuelson marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| // when forceRefresh = true the in-memory cache gets wiped out and the flow fetches from remote only. | ||
| val variationsResult = fetchFirstVariationsPage(productId, forceRefresh = true).last() | ||
| Result.success(variationsResult) | ||
| } | ||
|
|
||
| companion object { | ||
| private const val NORMAL_PAGE_SIZE = 25 | ||
| private const val PRE_POPULATION_PAGE_SIZE = 100 | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
❓ Is this intentionally returning
Successwhen site is not selected?