-
Notifications
You must be signed in to change notification settings - Fork 130
New issue
Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? # to your account
[Woo POS][Coupons] Refactor WooPosProductsDataSource
to Introduce WooPosBaseDataSource
for Improved Maintainability
#13764
base: trunk
Are you sure you want to change the base?
Changes from all commits
d1d8ea4
98c8548
7c7a706
24b0e37
5adc288
747e86c
def5811
2e1d3ac
88370e3
36ff06c
e1b379b
18b2635
2df2d8f
35e8718
2c6d30a
75ff8e1
c8f64bd
98385d4
a572dbe
a689dde
3049c64
9ee1b5e
f949718
6715443
7f7782f
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 |
---|---|---|
@@ -0,0 +1,9 @@ | ||
package com.woocommerce.android.ui.woopos.home.items.common | ||
|
||
import kotlinx.coroutines.flow.Flow | ||
|
||
interface FetchDataSource<T> { | ||
fun fetchData( | ||
fetchOptions: FetchOptions | ||
): Flow<FetchResult<T>> | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
package com.woocommerce.android.ui.woopos.home.items.common | ||
|
||
data class FetchOptions( | ||
val productId: Long? = null, | ||
val forceRefresh: Boolean = false, | ||
) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
package com.woocommerce.android.ui.woopos.home.items.common | ||
|
||
sealed class FetchResult<T> { | ||
data class Cached<T>(val data: List<T>) : FetchResult<T>() | ||
data class Remote<T>(val result: Result<List<T>>) : FetchResult<T>() | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
package com.woocommerce.android.ui.woopos.home.items.common | ||
|
||
import kotlinx.coroutines.Dispatchers | ||
import kotlinx.coroutines.flow.Flow | ||
import kotlinx.coroutines.flow.flow | ||
import kotlinx.coroutines.flow.flowOn | ||
|
||
abstract class WooPosBaseDataSource<T> : FetchDataSource<T> { | ||
|
||
protected abstract suspend fun fetchFromCache(fetchOptions: FetchOptions): List<T> | ||
protected abstract suspend fun fetchFromRemote( | ||
fetchOptions: FetchOptions, | ||
): Result<List<T>> | ||
protected abstract suspend fun updateCache(fetchOptions: FetchOptions, data: List<T>) | ||
|
||
override fun fetchData( | ||
fetchOptions: FetchOptions | ||
): Flow<FetchResult<T>> = flow { | ||
if (fetchOptions.forceRefresh) { | ||
updateCache(fetchOptions, data = emptyList()) | ||
} | ||
Comment on lines
+16
to
+21
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. 🛠️ Refactor suggestion Consider mutex or other concurrency safeguards for force-refresh cache clearing. When |
||
|
||
val cachedData = fetchFromCache(fetchOptions) | ||
emit(FetchResult.Cached(cachedData)) | ||
|
||
val remoteResult = fetchFromRemote(fetchOptions) | ||
if (remoteResult.isSuccess) { | ||
val remoteData = remoteResult.getOrThrow() | ||
updateCache(fetchOptions = fetchOptions, data = remoteData) | ||
emit(FetchResult.Remote(Result.success(remoteData))) | ||
} else { | ||
emit( | ||
FetchResult.Remote( | ||
Result.failure(remoteResult.exceptionOrNull() ?: Exception("Unknown error")) | ||
) | ||
) | ||
} | ||
}.flowOn(Dispatchers.IO) | ||
} |
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -3,13 +3,11 @@ package com.woocommerce.android.ui.woopos.home.items.products | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
import com.woocommerce.android.model.Product | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
import com.woocommerce.android.ui.products.ProductStatus | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
import com.woocommerce.android.ui.products.selector.ProductListHandler | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
import com.woocommerce.android.ui.woopos.home.items.common.FetchOptions | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
import com.woocommerce.android.ui.woopos.home.items.common.WooPosBaseDataSource | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
import com.woocommerce.android.util.WooLog | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
import kotlinx.coroutines.Dispatchers | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
import kotlinx.coroutines.flow.Flow | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
import kotlinx.coroutines.flow.first | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
import kotlinx.coroutines.flow.flow | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
import kotlinx.coroutines.flow.flowOn | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
import kotlinx.coroutines.flow.take | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
import kotlinx.coroutines.sync.Mutex | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
import kotlinx.coroutines.sync.withLock | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
import kotlinx.coroutines.withContext | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
@@ -20,46 +18,13 @@ import javax.inject.Singleton | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
@Singleton | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
class WooPosProductsDataSource @Inject constructor( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
private val handler: ProductListHandler, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
) : WooPosBaseDataSource<Product>() { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
private var productCache: List<Product> = emptyList() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
private val cacheMutex = Mutex() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
val hasMorePages: Boolean | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
get() = handler.canLoadMore.get() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
fun loadSimpleProducts(forceRefreshProducts: Boolean): Flow<ProductsResult> = flow { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
if (forceRefreshProducts) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
updateProductCache(emptyList()) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
emit(ProductsResult.Cached(productCache)) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
val result = handler.loadFromCacheAndFetch( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
forceRefresh = forceRefreshProducts, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
searchType = ProductListHandler.SearchType.DEFAULT, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
includeType = listOf(WCProductStore.IncludeType.Simple, WCProductStore.IncludeType.Variable), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
filters = mapOf( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
WCProductStore.ProductFilterOption.STATUS to ProductStatus.PUBLISH.value, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
WCProductStore.ProductFilterOption.DOWNLOADABLE to WCProductStore.DownloadableOptions.FALSE.toString(), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
if (result.isSuccess) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
val remoteProducts = handler.productsFlow.first() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
updateProductCache(remoteProducts) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
emit(ProductsResult.Remote(Result.success(productCache))) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} else { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
result.logFailure() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
emit( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
ProductsResult.Remote( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Result.failure( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
result.exceptionOrNull() ?: Exception("Unknown error") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
}.flowOn(Dispatchers.IO).take(2) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
suspend fun loadMore(): Result<List<Product>> = withContext(Dispatchers.IO) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
val result = handler.loadMore( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
includeTypes = listOf(WCProductStore.IncludeType.Simple, WCProductStore.IncludeType.Variable), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
@@ -88,4 +53,29 @@ class WooPosProductsDataSource @Inject constructor( | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
data class Cached(val products: List<Product>) : ProductsResult() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
data class Remote(val productsResult: Result<List<Product>>) : ProductsResult() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
override suspend fun fetchFromCache(fetchOptions: FetchOptions): List<Product> { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
return productCache | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
override suspend fun fetchFromRemote(fetchOptions: FetchOptions): Result<List<Product>> { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
val result = handler.loadFromCacheAndFetch( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
forceRefresh = true, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
includeType = listOf(WCProductStore.IncludeType.Simple, WCProductStore.IncludeType.Variable), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
searchType = ProductListHandler.SearchType.DEFAULT, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
filters = mapOf( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
WCProductStore.ProductFilterOption.STATUS to ProductStatus.PUBLISH.value, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
WCProductStore.ProductFilterOption.DOWNLOADABLE to WCProductStore.DownloadableOptions.FALSE.toString(), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
return if (result.isSuccess) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Result.success(handler.productsFlow.first()) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} else { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Result.failure(result.exceptionOrNull() ?: Exception("Unknown error while fetching products")) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Comment on lines
+61
to
+76
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. 🛠️ Refactor suggestion Remote fetch strategy. Using -val result = handler.loadFromCacheAndFetch(
- forceRefresh = true,
- ...
-)
+val result = handler.loadFromCacheAndFetch(
+ forceRefresh = fetchOptions.forceRefresh,
+ ...
+) 📝 Committable suggestion
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
override suspend fun updateCache(fetchOptions: FetchOptions, data: List<Product>) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
updateProductCache(data) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} |
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.
🤔 I'm trying to figure out where the interface would be used. Did you have any specific use case in mind, like DI, testing, etc. If we provide base class, maybe interface is not needed?