From e1aab9d6a05b34a6caa16e957b48294e25255016 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ce=CC=81sar=20Vargas=20Casaseca?= Date: Fri, 29 Aug 2025 11:09:16 +0200 Subject: [PATCH 01/22] First approach to cache --- .../android/ui/woopos/orders/WooPosOrdersCache.kt | 7 +++++++ .../ui/woopos/orders/WooPosOrdersInMemoryCache.kt | 13 +++++++++++++ 2 files changed, 20 insertions(+) create mode 100644 WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/orders/WooPosOrdersCache.kt create mode 100644 WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/orders/WooPosOrdersInMemoryCache.kt diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/orders/WooPosOrdersCache.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/orders/WooPosOrdersCache.kt new file mode 100644 index 000000000000..7ca8f33d2967 --- /dev/null +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/orders/WooPosOrdersCache.kt @@ -0,0 +1,7 @@ +package com.woocommerce.android.ui.woopos.orders + +import com.woocommerce.android.model.Order + +interface WooPosOrdersCache { + suspend fun addAll(orders: List) +} diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/orders/WooPosOrdersInMemoryCache.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/orders/WooPosOrdersInMemoryCache.kt new file mode 100644 index 000000000000..b5b819e10ae3 --- /dev/null +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/orders/WooPosOrdersInMemoryCache.kt @@ -0,0 +1,13 @@ +package com.woocommerce.android.ui.woopos.orders + +import com.woocommerce.android.model.Order +import kotlinx.coroutines.sync.Mutex +import kotlinx.coroutines.sync.withLock +import javax.inject.Inject + +class WooPosOrdersInMemoryCache @Inject constructor() : WooPosOrdersCache { + private val mutex = Mutex() + + override suspend fun addAll(orders: List) = mutex.withLock { + } +} From 3f9d131658eb13e0322f1be2e25b93c14111ef2f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ce=CC=81sar=20Vargas=20Casaseca?= Date: Fri, 29 Aug 2025 12:46:01 +0200 Subject: [PATCH 02/22] Implement cache and use it in view model --- .../woocommerce/android/di/WooPosModule.kt | 6 +++ .../data}/WooPosOrdersDataSource.kt | 26 ++++++--- .../common/data/WooPosOrdersInMemoryCache.kt | 37 +++++++++++++ .../ui/woopos/orders/WooPosOrdersCache.kt | 2 + .../orders/WooPosOrdersInMemoryCache.kt | 13 ----- .../ui/woopos/orders/WooPosOrdersViewModel.kt | 54 ++++++++++++------- 6 files changed, 98 insertions(+), 40 deletions(-) rename WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/{orders => common/data}/WooPosOrdersDataSource.kt (53%) create mode 100644 WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/common/data/WooPosOrdersInMemoryCache.kt delete mode 100644 WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/orders/WooPosOrdersInMemoryCache.kt diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/di/WooPosModule.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/di/WooPosModule.kt index 547c8079e25c..667bdd39c1c8 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/di/WooPosModule.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/di/WooPosModule.kt @@ -1,7 +1,9 @@ package com.woocommerce.android.di +import com.woocommerce.android.ui.woopos.common.data.WooPosOrdersInMemoryCache import com.woocommerce.android.ui.woopos.common.data.WooPosProductsCache import com.woocommerce.android.ui.woopos.common.data.WooPosProductsInMemoryCache +import com.woocommerce.android.ui.woopos.orders.WooPosOrdersCache import dagger.Binds import dagger.Module import dagger.hilt.InstallIn @@ -14,4 +16,8 @@ abstract class WooPosModule { @Binds @Singleton abstract fun provideWooPosProductsCache(implementation: WooPosProductsInMemoryCache): WooPosProductsCache + + @Binds + @Singleton + abstract fun provideWooPosOrdersCache(implementation: WooPosOrdersInMemoryCache): WooPosOrdersCache } diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/orders/WooPosOrdersDataSource.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/common/data/WooPosOrdersDataSource.kt similarity index 53% rename from WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/orders/WooPosOrdersDataSource.kt rename to WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/common/data/WooPosOrdersDataSource.kt index b167a2cc5d23..f5a4ba5f2384 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/orders/WooPosOrdersDataSource.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/common/data/WooPosOrdersDataSource.kt @@ -1,9 +1,11 @@ -package com.woocommerce.android.ui.woopos.orders +package com.woocommerce.android.ui.woopos.common.data import com.woocommerce.android.model.Order import com.woocommerce.android.model.OrderMapper -import com.woocommerce.android.model.toAppModel import com.woocommerce.android.tools.SelectedSite +import com.woocommerce.android.ui.woopos.orders.WooPosOrdersCache +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flow import org.wordpress.android.fluxc.network.rest.wpcom.wc.WooResult import org.wordpress.android.fluxc.persistence.entity.OrderEntity import org.wordpress.android.fluxc.store.WCOrderStore @@ -13,14 +15,19 @@ class WooPosOrdersDataSource @Inject constructor( private val orderStore: WCOrderStore, private val selectedSite: SelectedSite, private val orderMapper: OrderMapper, + private val ordersCache: WooPosOrdersCache, ) { - suspend fun loadOrders(): WooResult> { - val result = fetchOrdersFromStore(1) + fun loadOrders(): Flow = flow { + val cached = ordersCache.getAll() + emit(OrdersResult.Cached(cached)) - return if (result.isError) { - WooResult(result.error) + val result = fetchOrdersFromStore(page = 1) + if (result.isError) { + emit(OrdersResult.Remote(Result.failure(Exception(result.error.message)))) } else { - WooResult(result.model.toAppModels()) + val mapped = result.model.toAppModels() + ordersCache.addAll(mapped) + emit(OrdersResult.Remote(Result.success(mapped))) } } @@ -38,4 +45,9 @@ class WooPosOrdersDataSource @Inject constructor( private suspend fun List?.toAppModels(): List = this?.map { orderMapper.toAppModel(it) } ?: emptyList() + + sealed class OrdersResult { + data class Cached(val orders: List) : OrdersResult() + data class Remote(val ordersResult: Result>) : OrdersResult() + } } diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/common/data/WooPosOrdersInMemoryCache.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/common/data/WooPosOrdersInMemoryCache.kt new file mode 100644 index 000000000000..d114f6bfe607 --- /dev/null +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/common/data/WooPosOrdersInMemoryCache.kt @@ -0,0 +1,37 @@ +package com.woocommerce.android.ui.woopos.common.data + +import com.woocommerce.android.model.Order +import com.woocommerce.android.ui.woopos.common.data.WooPosProductsCache.Companion.MAX_CACHE_SIZE +import com.woocommerce.android.ui.woopos.orders.WooPosOrdersCache +import kotlinx.coroutines.sync.Mutex +import kotlinx.coroutines.sync.withLock +import javax.inject.Inject + +class WooPosOrdersInMemoryCache @Inject constructor() : WooPosOrdersCache { + private val mutex = Mutex() + + companion object { + private const val INITIAL_CAPACITY = 25 + private const val LOAD_FACTOR = 0.75f + } + + private val ordersCache = LinkedHashMap(INITIAL_CAPACITY, LOAD_FACTOR, true) + + override suspend fun addAll(orders: List) = mutex.withLock { + addAllInternal(orders) + } + + override suspend fun getAll(): List = mutex.withLock { + ordersCache.values.toList() + } + + private fun addAllInternal(orders: List) { + orders.forEach { order -> + ordersCache[order.id] = order + if (ordersCache.size > MAX_CACHE_SIZE) { + val keysToRemove = ordersCache.keys.take(ordersCache.size - MAX_CACHE_SIZE) + keysToRemove.forEach { ordersCache.remove(it) } + } + } + } +} diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/orders/WooPosOrdersCache.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/orders/WooPosOrdersCache.kt index 7ca8f33d2967..46d9e9553dca 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/orders/WooPosOrdersCache.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/orders/WooPosOrdersCache.kt @@ -4,4 +4,6 @@ import com.woocommerce.android.model.Order interface WooPosOrdersCache { suspend fun addAll(orders: List) + + suspend fun getAll(): List } diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/orders/WooPosOrdersInMemoryCache.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/orders/WooPosOrdersInMemoryCache.kt deleted file mode 100644 index b5b819e10ae3..000000000000 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/orders/WooPosOrdersInMemoryCache.kt +++ /dev/null @@ -1,13 +0,0 @@ -package com.woocommerce.android.ui.woopos.orders - -import com.woocommerce.android.model.Order -import kotlinx.coroutines.sync.Mutex -import kotlinx.coroutines.sync.withLock -import javax.inject.Inject - -class WooPosOrdersInMemoryCache @Inject constructor() : WooPosOrdersCache { - private val mutex = Mutex() - - override suspend fun addAll(orders: List) = mutex.withLock { - } -} diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/orders/WooPosOrdersViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/orders/WooPosOrdersViewModel.kt index 01ef7c08561d..a7f431dae7ea 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/orders/WooPosOrdersViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/orders/WooPosOrdersViewModel.kt @@ -2,6 +2,7 @@ package com.woocommerce.android.ui.woopos.orders import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +import com.woocommerce.android.ui.woopos.common.data.WooPosOrdersDataSource import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow @@ -26,26 +27,39 @@ class WooPosOrdersViewModel @Inject constructor( viewModelScope.launch { _state.update { it.copy(isLoading = true, error = null) } - val result = ordersDataSource.loadOrders() - - if (result.isError || result.model == null) { - _state.update { - it.copy( - isLoading = false, - error = result.error?.message ?: "Unknown error" - ) - } - return@launch - } - - result.model?.let { list -> - _state.update { prev -> - prev.copy( - isLoading = false, - orders = list, - selectedOrderId = prev.selectedOrderId?.takeIf { id -> list.any { o -> o.id == id } } - ?: list.firstOrNull()?.id - ) + ordersDataSource.loadOrders().collect { res -> + when (res) { + is WooPosOrdersDataSource.OrdersResult.Cached -> { + // Show cache immediately, keep loading=true while remote is in flight + val list = res.orders + _state.update { prev -> + prev.copy( + orders = list, + // preserve selection if still present; otherwise pick first + selectedOrderId = prev.selectedOrderId?.takeIf { id -> list.any { o -> o.id == id } } + ?: list.firstOrNull()?.id + ) + } + } + is WooPosOrdersDataSource.OrdersResult.Remote -> { + res.ordersResult.fold( + onSuccess = { list -> + _state.update { prev -> + prev.copy( + isLoading = false, + error = null, + orders = list, + selectedOrderId = prev.selectedOrderId + ?.takeIf { id -> list.any { o -> o.id == id } } + ?: list.firstOrNull()?.id + ) + } + }, + onFailure = { err -> + _state.update { it.copy(isLoading = false, error = err.message ?: "Unknown error") } + } + ) + } } } } From a77834d3d118056b162b5d71aa3e4916d6cc8703 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ce=CC=81sar=20Vargas=20Casaseca?= Date: Fri, 29 Aug 2025 12:46:32 +0200 Subject: [PATCH 03/22] Remove comments --- .../android/ui/woopos/orders/WooPosOrdersViewModel.kt | 2 -- 1 file changed, 2 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/orders/WooPosOrdersViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/orders/WooPosOrdersViewModel.kt index a7f431dae7ea..62fee5e3f917 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/orders/WooPosOrdersViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/orders/WooPosOrdersViewModel.kt @@ -30,12 +30,10 @@ class WooPosOrdersViewModel @Inject constructor( ordersDataSource.loadOrders().collect { res -> when (res) { is WooPosOrdersDataSource.OrdersResult.Cached -> { - // Show cache immediately, keep loading=true while remote is in flight val list = res.orders _state.update { prev -> prev.copy( orders = list, - // preserve selection if still present; otherwise pick first selectedOrderId = prev.selectedOrderId?.takeIf { id -> list.any { o -> o.id == id } } ?: list.firstOrNull()?.id ) From 659ad10aa00eaa4b16c80cdacaf8843b3561b561 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ce=CC=81sar=20Vargas=20Casaseca?= Date: Fri, 29 Aug 2025 14:01:58 +0200 Subject: [PATCH 04/22] Show the cached content right away --- .../android/ui/woopos/orders/WooPosOrdersViewModel.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/orders/WooPosOrdersViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/orders/WooPosOrdersViewModel.kt index 62fee5e3f917..5a681c7b208d 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/orders/WooPosOrdersViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/orders/WooPosOrdersViewModel.kt @@ -33,6 +33,7 @@ class WooPosOrdersViewModel @Inject constructor( val list = res.orders _state.update { prev -> prev.copy( + isLoading = false, orders = list, selectedOrderId = prev.selectedOrderId?.takeIf { id -> list.any { o -> o.id == id } } ?: list.firstOrNull()?.id From 4a5f93692190ce22f986dc4de18b6a8400621302 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ce=CC=81sar=20Vargas=20Casaseca?= Date: Fri, 29 Aug 2025 15:24:04 +0200 Subject: [PATCH 05/22] Clear cache when leaving POS --- .../woopos/common/data/WooPosOrdersInMemoryCache.kt | 4 ++++ .../android/ui/woopos/orders/WooPosOrdersCache.kt | 2 ++ .../android/ui/woopos/root/WooPosActivity.kt | 12 ++++++++++++ 3 files changed, 18 insertions(+) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/common/data/WooPosOrdersInMemoryCache.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/common/data/WooPosOrdersInMemoryCache.kt index d114f6bfe607..40b979ac290e 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/common/data/WooPosOrdersInMemoryCache.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/common/data/WooPosOrdersInMemoryCache.kt @@ -25,6 +25,10 @@ class WooPosOrdersInMemoryCache @Inject constructor() : WooPosOrdersCache { ordersCache.values.toList() } + override suspend fun clear() = mutex.withLock { + ordersCache.clear() + } + private fun addAllInternal(orders: List) { orders.forEach { order -> ordersCache[order.id] = order diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/orders/WooPosOrdersCache.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/orders/WooPosOrdersCache.kt index 46d9e9553dca..bd7909f85ce7 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/orders/WooPosOrdersCache.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/orders/WooPosOrdersCache.kt @@ -6,4 +6,6 @@ interface WooPosOrdersCache { suspend fun addAll(orders: List) suspend fun getAll(): List + + suspend fun clear() } diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/root/WooPosActivity.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/root/WooPosActivity.kt index 615069cff3a8..74497906ec6c 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/root/WooPosActivity.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/root/WooPosActivity.kt @@ -15,10 +15,12 @@ import com.woocommerce.android.ui.woopos.cardreader.WooPosCardReaderFacade import com.woocommerce.android.ui.woopos.common.composeui.designsystem.WooPosSpacing import com.woocommerce.android.ui.woopos.common.composeui.designsystem.WooPosTheme import com.woocommerce.android.ui.woopos.home.items.coupons.creation.WooPosCouponCreationFacade +import com.woocommerce.android.ui.woopos.orders.WooPosOrdersCache import com.woocommerce.android.ui.woopos.support.WooPosGetSupportFacade import com.woocommerce.android.ui.woopos.util.analytics.WooPosAnalyticsTracker import com.woocommerce.android.ui.woopos.util.ext.isGestureNavigation import dagger.hilt.android.AndroidEntryPoint +import kotlinx.coroutines.runBlocking import javax.inject.Inject @AndroidEntryPoint @@ -35,6 +37,9 @@ class WooPosActivity : AppCompatActivity() { @Inject lateinit var wooPosCouponCreationFacade: WooPosCouponCreationFacade + @Inject + lateinit var ordersCache: WooPosOrdersCache + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) WindowCompat.setDecorFitsSystemWindows(window, false) @@ -53,6 +58,13 @@ class WooPosActivity : AppCompatActivity() { } } } + + override fun onDestroy() { + runBlocking { + ordersCache.clear() + } + super.onDestroy() + } } @Composable From 2bc71187ebc783bc7e5582ba9825b298eb465410 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ce=CC=81sar=20Vargas=20Casaseca?= Date: Fri, 29 Aug 2025 15:40:45 +0200 Subject: [PATCH 06/22] Add max size for orders cache --- .../ui/woopos/common/data/WooPosOrdersInMemoryCache.kt | 4 ++-- .../woocommerce/android/ui/woopos/orders/WooPosOrdersCache.kt | 4 ++++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/common/data/WooPosOrdersInMemoryCache.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/common/data/WooPosOrdersInMemoryCache.kt index 40b979ac290e..13a936c91a7a 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/common/data/WooPosOrdersInMemoryCache.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/common/data/WooPosOrdersInMemoryCache.kt @@ -1,8 +1,8 @@ package com.woocommerce.android.ui.woopos.common.data import com.woocommerce.android.model.Order -import com.woocommerce.android.ui.woopos.common.data.WooPosProductsCache.Companion.MAX_CACHE_SIZE import com.woocommerce.android.ui.woopos.orders.WooPosOrdersCache +import com.woocommerce.android.ui.woopos.orders.WooPosOrdersCache.Companion.MAX_CACHE_SIZE import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock import javax.inject.Inject @@ -11,7 +11,7 @@ class WooPosOrdersInMemoryCache @Inject constructor() : WooPosOrdersCache { private val mutex = Mutex() companion object { - private const val INITIAL_CAPACITY = 25 + private const val INITIAL_CAPACITY = MAX_CACHE_SIZE private const val LOAD_FACTOR = 0.75f } diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/orders/WooPosOrdersCache.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/orders/WooPosOrdersCache.kt index bd7909f85ce7..42e4e1c680b6 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/orders/WooPosOrdersCache.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/orders/WooPosOrdersCache.kt @@ -3,6 +3,10 @@ package com.woocommerce.android.ui.woopos.orders import com.woocommerce.android.model.Order interface WooPosOrdersCache { + companion object { + const val MAX_CACHE_SIZE = 25 + } + suspend fun addAll(orders: List) suspend fun getAll(): List From 0a349b748c86adc211d0c6571c85ed1e801864b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ce=CC=81sar=20Vargas=20Casaseca?= Date: Fri, 29 Aug 2025 15:41:02 +0200 Subject: [PATCH 07/22] Add tests for cache --- .../data/WooPosOrdersInMemoryCacheTest.kt | 134 ++++++++++++++++++ 1 file changed, 134 insertions(+) create mode 100644 WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/common/data/WooPosOrdersInMemoryCacheTest.kt diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/common/data/WooPosOrdersInMemoryCacheTest.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/common/data/WooPosOrdersInMemoryCacheTest.kt new file mode 100644 index 000000000000..c3f7422e32e6 --- /dev/null +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/common/data/WooPosOrdersInMemoryCacheTest.kt @@ -0,0 +1,134 @@ +package com.woocommerce.android.ui.woopos.common.data + +import com.woocommerce.android.model.Order +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.async +import kotlinx.coroutines.awaitAll +import kotlinx.coroutines.test.runTest +import org.assertj.core.api.Assertions.assertThat +import org.junit.Before +import org.junit.Test +import org.mockito.kotlin.mock +import java.math.BigDecimal +import java.util.Date + +@ExperimentalCoroutinesApi +class WooPosOrdersInMemoryCacheTest { + + private lateinit var cache: WooPosOrdersInMemoryCache + + @Before + fun setup() { + cache = WooPosOrdersInMemoryCache() + } + + @Test + fun `when cache is empty, then getAll returns empty list`() = runTest { + // WHEN + val result = cache.getAll() + + // THEN + assertThat(result).isEmpty() + } + + @Test + fun `when orders are added, then getAll returns all orders`() = runTest { + // GIVEN + val orders = listOf(createTestOrder(1), createTestOrder(2)) + + // WHEN + cache.addAll(orders) + val result = cache.getAll() + + // THEN + assertThat(result).hasSize(2) + assertThat(result).containsExactlyInAnyOrderElementsOf(orders) + } + + @Test + fun `when cache is cleared, then getAll returns empty list`() = runTest { + // GIVEN + val orders = listOf(createTestOrder(1), createTestOrder(2)) + cache.addAll(orders) + + // WHEN + cache.clear() + val result = cache.getAll() + + // THEN + assertThat(result).isEmpty() + } + + @Test + fun `when adding order with existing id, then it should replace old order`() = runTest { + // GIVEN + val order1 = createTestOrder(1, "Order 1") + val order1Updated = createTestOrder(1, "Order 1 Updated") + + // WHEN + cache.addAll(listOf(order1)) + cache.addAll(listOf(order1Updated)) + val result = cache.getAll().first { it.id == 1L } + + // THEN + assertThat(result).isEqualTo(order1Updated) + assertThat(result.customerNote).isEqualTo("Order 1 Updated") + } + + @Test + fun `when adding more orders than max cache size, then oldest orders should be removed`() = runTest { + // GIVEN + val orders = (1..10005L).map { createTestOrder(it) } + + // WHEN + cache.addAll(orders) + val firstOrder = cache.getAll().find { it.id == 1L } + val lastOrder = cache.getAll().find { it.id == 10005L } + + // THEN + assertThat(firstOrder).isNull() + assertThat(lastOrder).isNotNull + assertThat(cache.getAll()).hasSize(25) + } + + @Test + fun `when multiple threads access cache concurrently, then data remains consistent`() = runTest { + // GIVEN + val initialOrders = (1..100L).map { createTestOrder(it) } + cache.addAll(initialOrders) + + // WHEN - simulate concurrent access + val concurrentOperations = (101..200L).map { id -> + async { + val order = createTestOrder(id) + cache.addAll(listOf(order)) + cache.getAll().find { it.id == id } + } + } + + // THEN + val results = concurrentOperations.awaitAll() + assertThat(results).hasSize(100) + assertThat(results).doesNotContainNull() + + val allCachedOrders = cache.getAll() + // Only MAX_CACHE_SIZE should remain + assertThat(allCachedOrders).hasSize(25) + + // The most recent 25 orders should be present + val expectedIds = (176L..200L).toList() + assertThat(allCachedOrders.map { it.id }).containsExactlyInAnyOrderElementsOf(expectedIds) + } + + private fun createTestOrder( + id: Long, + note: String = "Order $id" + ): Order = mock { + on { this.id }.thenReturn(id) + on { customerNote }.thenReturn(note) + on { dateCreated }.thenReturn(Date()) + on { total }.thenReturn(BigDecimal.TEN) + on { currency }.thenReturn("USD") + on { number }.thenReturn(id.toString()) + } +} From 7f2acde444523a04dbef48b28ae2e4846e9d89a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ce=CC=81sar=20Vargas=20Casaseca?= Date: Fri, 29 Aug 2025 16:59:03 +0200 Subject: [PATCH 08/22] Bring data source back to the orders package --- .../woopos/{common/data => orders}/WooPosOrdersDataSource.kt | 3 +-- .../android/ui/woopos/orders/WooPosOrdersViewModel.kt | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) rename WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/{common/data => orders}/WooPosOrdersDataSource.kt (93%) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/common/data/WooPosOrdersDataSource.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/orders/WooPosOrdersDataSource.kt similarity index 93% rename from WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/common/data/WooPosOrdersDataSource.kt rename to WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/orders/WooPosOrdersDataSource.kt index f5a4ba5f2384..41317483a131 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/common/data/WooPosOrdersDataSource.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/orders/WooPosOrdersDataSource.kt @@ -1,9 +1,8 @@ -package com.woocommerce.android.ui.woopos.common.data +package com.woocommerce.android.ui.woopos.orders import com.woocommerce.android.model.Order import com.woocommerce.android.model.OrderMapper import com.woocommerce.android.tools.SelectedSite -import com.woocommerce.android.ui.woopos.orders.WooPosOrdersCache import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.flow import org.wordpress.android.fluxc.network.rest.wpcom.wc.WooResult diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/orders/WooPosOrdersViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/orders/WooPosOrdersViewModel.kt index 5a681c7b208d..1b0ac7c4b168 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/orders/WooPosOrdersViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/orders/WooPosOrdersViewModel.kt @@ -2,7 +2,7 @@ package com.woocommerce.android.ui.woopos.orders import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope -import com.woocommerce.android.ui.woopos.common.data.WooPosOrdersDataSource +import com.woocommerce.android.ui.woopos.orders.WooPosOrdersDataSource import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow From 96c3a09bedf632d92f2cb702cac3eb1e937ba1a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ce=CC=81sar=20Vargas=20Casaseca?= Date: Thu, 4 Sep 2025 11:38:03 +0200 Subject: [PATCH 09/22] Fix test --- .../orders/WooPosOrdersDataSourceTest.kt | 78 ++++++++++++++----- 1 file changed, 58 insertions(+), 20 deletions(-) diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/orders/WooPosOrdersDataSourceTest.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/orders/WooPosOrdersDataSourceTest.kt index 42d466f48440..83e65c3ceee4 100644 --- a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/orders/WooPosOrdersDataSourceTest.kt +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/orders/WooPosOrdersDataSourceTest.kt @@ -5,6 +5,7 @@ import com.woocommerce.android.tools.SelectedSite import com.woocommerce.android.ui.orders.OrderTestUtils import com.woocommerce.android.ui.woopos.util.WooPosCoroutineTestRule import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.toList import kotlinx.coroutines.test.runTest import org.assertj.core.api.Assertions.assertThat import org.junit.Rule @@ -12,6 +13,7 @@ import org.mockito.kotlin.any import org.mockito.kotlin.anyOrNull import org.mockito.kotlin.eq import org.mockito.kotlin.mock +import org.mockito.kotlin.never import org.mockito.kotlin.verify import org.mockito.kotlin.whenever import org.wordpress.android.fluxc.model.LocalOrRemoteId @@ -33,25 +35,35 @@ class WooPosOrdersDataSourceTest { private val siteModel: SiteModel = mock() private val selectedSite: SelectedSite = mock { on { get() }.thenReturn(siteModel) } private val orderMapper: OrderMapper = mock() + private val ordersCache: WooPosOrdersCache = mock() private val sut = WooPosOrdersDataSource( restClient = orderRestClient, selectedSite = selectedSite, - orderMapper = orderMapper + orderMapper = orderMapper, + ordersCache = ordersCache ) @Test - fun `given rest client returns entities, when loadOrders called, then should map them to app models`() = runTest { + fun `given cache and successful fetch, when loadOrders collected, then emits cache first then mapped network and stores in cache`() = runTest { // GIVEN + val cachedOrder = OrderTestUtils.generateTestOrder() + whenever(ordersCache.getAll()).thenReturn(listOf(cachedOrder)) + + // Network returns two entities val e1 = OrderEntity(localSiteId = LocalOrRemoteId.LocalId(1), 1) val e2 = OrderEntity(localSiteId = LocalOrRemoteId.LocalId(1), 2) val entities = listOf( e1 to emptyList(), e2 to emptyList() ) + val firstOrder = OrderTestUtils.generateTestOrder() val secondOrder = OrderTestUtils.generateTestOrder() + whenever(orderMapper.toAppModel(e1)).thenReturn(firstOrder) + whenever(orderMapper.toAppModel(e2)).thenReturn(secondOrder) + val payload = WCOrderStore.FetchOrdersResponsePayload( site = siteModel, ordersWithMeta = entities @@ -69,18 +81,24 @@ class WooPosOrdersDataSourceTest { ) ).thenReturn(payload) - whenever(orderMapper.toAppModel(e1)).thenReturn(firstOrder) - whenever(orderMapper.toAppModel(e2)).thenReturn(secondOrder) - // WHEN - val result = sut.loadOrders() + val emissions = sut.loadOrders().toList(mutableListOf()) // THEN - assertThat(result).isInstanceOf(LoadOrdersResult.Success::class.java) - val success = result as LoadOrdersResult.Success - assertThat(success.orders).containsExactly(firstOrder, secondOrder) + assertThat(emissions).hasSize(2) + // First emission = cache + val first = emissions[0] + assertThat(first).isInstanceOf(LoadOrdersResult.Success::class.java) + assertThat((first as LoadOrdersResult.Success).orders).containsExactly(cachedOrder) + + // Second emission = network mapped + val second = emissions[1] + assertThat(second).isInstanceOf(LoadOrdersResult.Success::class.java) + assertThat((second as LoadOrdersResult.Success).orders).containsExactly(firstOrder, secondOrder) verify(selectedSite).get() + verify(ordersCache).getAll() + verify(ordersCache).addAll(listOf(firstOrder, secondOrder)) verify(orderRestClient).fetchOrders( site = eq(siteModel), count = eq(25), @@ -93,8 +111,11 @@ class WooPosOrdersDataSourceTest { } @Test - fun `given store returns error, when loadOrders called, then should return error result`() = runTest { + fun `given cache and fetch error, when loadOrders collected, then emits cache then error without caching`() = runTest { // GIVEN + val cachedOrder = OrderTestUtils.generateTestOrder() + whenever(ordersCache.getAll()).thenReturn(listOf(cachedOrder)) + val orderError = WCOrderStore.OrderError( type = WCOrderStore.OrderErrorType.GENERIC_ERROR, message = "generic error" @@ -118,13 +139,21 @@ class WooPosOrdersDataSourceTest { ).thenReturn(payload) // WHEN - val result = sut.loadOrders() + val emissions = sut.loadOrders().toList(mutableListOf()) // THEN - assertThat(result).isInstanceOf(LoadOrdersResult.Error::class.java) - val error = result as LoadOrdersResult.Error - assertThat(error.message).isEqualTo(orderError.message) + assertThat(emissions).hasSize(2) + + val first = emissions[0] + assertThat(first).isInstanceOf(LoadOrdersResult.Success::class.java) + assertThat((first as LoadOrdersResult.Success).orders).containsExactly(cachedOrder) + + val second = emissions[1] + assertThat(second).isInstanceOf(LoadOrdersResult.Error::class.java) + assertThat((second as LoadOrdersResult.Error).message).isEqualTo("generic error") + verify(ordersCache).getAll() + verify(ordersCache, never()).addAll(any()) verify(orderRestClient).fetchOrders( site = eq(siteModel), count = eq(25), @@ -137,13 +166,14 @@ class WooPosOrdersDataSourceTest { } @Test - fun `given default site and pagination, when loadOrders called, then should forward params including createdVia`() = runTest { + fun `given empty cache, when loadOrders collected, then forwards params including createdVia and emits empty then empty`() = runTest { // GIVEN + whenever(ordersCache.getAll()).thenReturn(emptyList()) + val payload = WCOrderStore.FetchOrdersResponsePayload( site = siteModel, ordersWithMeta = emptyList() ) - whenever( orderRestClient.fetchOrders( site = eq(siteModel), @@ -157,14 +187,22 @@ class WooPosOrdersDataSourceTest { ).thenReturn(payload) // WHEN - val result = sut.loadOrders() + val emissions = sut.loadOrders().toList(mutableListOf()) // THEN - assertThat(result).isInstanceOf(LoadOrdersResult.Success::class.java) - val success = result as LoadOrdersResult.Success - assertThat(success.orders).isEmpty() + assertThat(emissions).hasSize(2) + + val first = emissions[0] + assertThat(first).isInstanceOf(LoadOrdersResult.Success::class.java) + assertThat((first as LoadOrdersResult.Success).orders).isEmpty() + + val second = emissions[1] + assertThat(second).isInstanceOf(LoadOrdersResult.Success::class.java) + assertThat((second as LoadOrdersResult.Success).orders).isEmpty() verify(selectedSite).get() + verify(ordersCache).getAll() + verify(ordersCache).addAll(emptyList()) verify(orderRestClient).fetchOrders( site = eq(siteModel), count = eq(25), From a57f5d783095fc2fbbd728b38b9a4e38fb2a2471 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ce=CC=81sar=20Vargas=20Casaseca?= Date: Thu, 4 Sep 2025 16:47:36 +0200 Subject: [PATCH 10/22] Remove non necessary suspend --- .../android/ui/woopos/orders/WooPosOrdersDataSource.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/orders/WooPosOrdersDataSource.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/orders/WooPosOrdersDataSource.kt index 9d35cc160061..541a84dae28c 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/orders/WooPosOrdersDataSource.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/orders/WooPosOrdersDataSource.kt @@ -21,7 +21,7 @@ class WooPosOrdersDataSource @Inject constructor( private val orderMapper: OrderMapper, private val ordersCache: WooPosOrdersCache ) { - suspend fun loadOrders(): Flow = flow { + fun loadOrders(): Flow = flow { val cached = ordersCache.getAll() emit(LoadOrdersResult.Success(cached)) From 333effb6cc590a9ff48c1f8babfe9d9c7c20249b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ce=CC=81sar=20Vargas=20Casaseca?= Date: Thu, 4 Sep 2025 16:48:50 +0200 Subject: [PATCH 11/22] Move file to its right package --- .../main/kotlin/com/woocommerce/android/di/WooPosModule.kt | 2 +- .../ui/woopos/common/data/WooPosOrdersInMemoryCache.kt | 4 ++-- .../data/searchbyidentifier}/WooPosOrdersCache.kt | 2 +- .../android/ui/woopos/orders/WooPosOrdersDataSource.kt | 1 + .../com/woocommerce/android/ui/woopos/root/WooPosActivity.kt | 2 +- .../android/ui/woopos/orders/WooPosOrdersDataSourceTest.kt | 1 + 6 files changed, 7 insertions(+), 5 deletions(-) rename WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/{orders => common/data/searchbyidentifier}/WooPosOrdersCache.kt (77%) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/di/WooPosModule.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/di/WooPosModule.kt index 667bdd39c1c8..cd4bdbfad5da 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/di/WooPosModule.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/di/WooPosModule.kt @@ -3,7 +3,7 @@ package com.woocommerce.android.di import com.woocommerce.android.ui.woopos.common.data.WooPosOrdersInMemoryCache import com.woocommerce.android.ui.woopos.common.data.WooPosProductsCache import com.woocommerce.android.ui.woopos.common.data.WooPosProductsInMemoryCache -import com.woocommerce.android.ui.woopos.orders.WooPosOrdersCache +import com.woocommerce.android.ui.woopos.common.data.searchbyidentifier.WooPosOrdersCache import dagger.Binds import dagger.Module import dagger.hilt.InstallIn diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/common/data/WooPosOrdersInMemoryCache.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/common/data/WooPosOrdersInMemoryCache.kt index 13a936c91a7a..d820d6fae126 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/common/data/WooPosOrdersInMemoryCache.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/common/data/WooPosOrdersInMemoryCache.kt @@ -1,8 +1,8 @@ package com.woocommerce.android.ui.woopos.common.data import com.woocommerce.android.model.Order -import com.woocommerce.android.ui.woopos.orders.WooPosOrdersCache -import com.woocommerce.android.ui.woopos.orders.WooPosOrdersCache.Companion.MAX_CACHE_SIZE +import com.woocommerce.android.ui.woopos.common.data.searchbyidentifier.WooPosOrdersCache +import com.woocommerce.android.ui.woopos.common.data.searchbyidentifier.WooPosOrdersCache.Companion.MAX_CACHE_SIZE import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock import javax.inject.Inject diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/orders/WooPosOrdersCache.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/common/data/searchbyidentifier/WooPosOrdersCache.kt similarity index 77% rename from WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/orders/WooPosOrdersCache.kt rename to WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/common/data/searchbyidentifier/WooPosOrdersCache.kt index 42e4e1c680b6..51addf331b14 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/orders/WooPosOrdersCache.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/common/data/searchbyidentifier/WooPosOrdersCache.kt @@ -1,4 +1,4 @@ -package com.woocommerce.android.ui.woopos.orders +package com.woocommerce.android.ui.woopos.common.data.searchbyidentifier import com.woocommerce.android.model.Order diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/orders/WooPosOrdersDataSource.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/orders/WooPosOrdersDataSource.kt index 541a84dae28c..25f7f6c0fe51 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/orders/WooPosOrdersDataSource.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/orders/WooPosOrdersDataSource.kt @@ -3,6 +3,7 @@ package com.woocommerce.android.ui.woopos.orders import com.woocommerce.android.model.Order import com.woocommerce.android.model.OrderMapper import com.woocommerce.android.tools.SelectedSite +import com.woocommerce.android.ui.woopos.common.data.searchbyidentifier.WooPosOrdersCache import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.flow import org.wordpress.android.fluxc.network.rest.wpcom.wc.order.OrderRestClient diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/root/WooPosActivity.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/root/WooPosActivity.kt index 74497906ec6c..b99dcd0b7217 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/root/WooPosActivity.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/root/WooPosActivity.kt @@ -15,7 +15,7 @@ import com.woocommerce.android.ui.woopos.cardreader.WooPosCardReaderFacade import com.woocommerce.android.ui.woopos.common.composeui.designsystem.WooPosSpacing import com.woocommerce.android.ui.woopos.common.composeui.designsystem.WooPosTheme import com.woocommerce.android.ui.woopos.home.items.coupons.creation.WooPosCouponCreationFacade -import com.woocommerce.android.ui.woopos.orders.WooPosOrdersCache +import com.woocommerce.android.ui.woopos.common.data.searchbyidentifier.WooPosOrdersCache import com.woocommerce.android.ui.woopos.support.WooPosGetSupportFacade import com.woocommerce.android.ui.woopos.util.analytics.WooPosAnalyticsTracker import com.woocommerce.android.ui.woopos.util.ext.isGestureNavigation diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/orders/WooPosOrdersDataSourceTest.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/orders/WooPosOrdersDataSourceTest.kt index 83e65c3ceee4..1e2c257c5f74 100644 --- a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/orders/WooPosOrdersDataSourceTest.kt +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/orders/WooPosOrdersDataSourceTest.kt @@ -3,6 +3,7 @@ package com.woocommerce.android.ui.woopos.orders import com.woocommerce.android.model.OrderMapper import com.woocommerce.android.tools.SelectedSite import com.woocommerce.android.ui.orders.OrderTestUtils +import com.woocommerce.android.ui.woopos.common.data.searchbyidentifier.WooPosOrdersCache import com.woocommerce.android.ui.woopos.util.WooPosCoroutineTestRule import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.toList From 60d1cf9057ffd946913ad324f720ee0d54b5c606 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ce=CC=81sar=20Vargas=20Casaseca?= Date: Thu, 4 Sep 2025 18:02:07 +0200 Subject: [PATCH 12/22] Fix detekt --- .../com/woocommerce/android/ui/woopos/root/WooPosActivity.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/root/WooPosActivity.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/root/WooPosActivity.kt index b99dcd0b7217..ac32c3ccc3a7 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/root/WooPosActivity.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/root/WooPosActivity.kt @@ -14,8 +14,8 @@ import androidx.core.view.WindowInsetsCompat import com.woocommerce.android.ui.woopos.cardreader.WooPosCardReaderFacade import com.woocommerce.android.ui.woopos.common.composeui.designsystem.WooPosSpacing import com.woocommerce.android.ui.woopos.common.composeui.designsystem.WooPosTheme -import com.woocommerce.android.ui.woopos.home.items.coupons.creation.WooPosCouponCreationFacade import com.woocommerce.android.ui.woopos.common.data.searchbyidentifier.WooPosOrdersCache +import com.woocommerce.android.ui.woopos.home.items.coupons.creation.WooPosCouponCreationFacade import com.woocommerce.android.ui.woopos.support.WooPosGetSupportFacade import com.woocommerce.android.ui.woopos.util.analytics.WooPosAnalyticsTracker import com.woocommerce.android.ui.woopos.util.ext.isGestureNavigation From 69cc4dd547f8e95b2ca1d2e9f3f19ddd3509b640 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ce=CC=81sar=20Vargas=20Casaseca?= Date: Fri, 5 Sep 2025 10:57:27 +0200 Subject: [PATCH 13/22] fix lint and simplify test --- .../data/WooPosOrdersInMemoryCacheTest.kt | 33 +++++-------------- 1 file changed, 9 insertions(+), 24 deletions(-) diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/common/data/WooPosOrdersInMemoryCacheTest.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/common/data/WooPosOrdersInMemoryCacheTest.kt index c3f7422e32e6..a1c291347157 100644 --- a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/common/data/WooPosOrdersInMemoryCacheTest.kt +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/common/data/WooPosOrdersInMemoryCacheTest.kt @@ -1,6 +1,6 @@ package com.woocommerce.android.ui.woopos.common.data -import com.woocommerce.android.model.Order +import com.woocommerce.android.ui.orders.OrderTestUtils import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.async import kotlinx.coroutines.awaitAll @@ -8,9 +8,6 @@ import kotlinx.coroutines.test.runTest import org.assertj.core.api.Assertions.assertThat import org.junit.Before import org.junit.Test -import org.mockito.kotlin.mock -import java.math.BigDecimal -import java.util.Date @ExperimentalCoroutinesApi class WooPosOrdersInMemoryCacheTest { @@ -34,7 +31,7 @@ class WooPosOrdersInMemoryCacheTest { @Test fun `when orders are added, then getAll returns all orders`() = runTest { // GIVEN - val orders = listOf(createTestOrder(1), createTestOrder(2)) + val orders = listOf(OrderTestUtils.generateTestOrder(1), OrderTestUtils.generateTestOrder(2)) // WHEN cache.addAll(orders) @@ -48,7 +45,7 @@ class WooPosOrdersInMemoryCacheTest { @Test fun `when cache is cleared, then getAll returns empty list`() = runTest { // GIVEN - val orders = listOf(createTestOrder(1), createTestOrder(2)) + val orders = listOf(OrderTestUtils.generateTestOrder(1), OrderTestUtils.generateTestOrder(2)) cache.addAll(orders) // WHEN @@ -62,8 +59,8 @@ class WooPosOrdersInMemoryCacheTest { @Test fun `when adding order with existing id, then it should replace old order`() = runTest { // GIVEN - val order1 = createTestOrder(1, "Order 1") - val order1Updated = createTestOrder(1, "Order 1 Updated") + val order1 = OrderTestUtils.generateTestOrder(1) + val order1Updated = OrderTestUtils.generateTestOrder(1).copy(currency = "TEST") // WHEN cache.addAll(listOf(order1)) @@ -72,13 +69,13 @@ class WooPosOrdersInMemoryCacheTest { // THEN assertThat(result).isEqualTo(order1Updated) - assertThat(result.customerNote).isEqualTo("Order 1 Updated") + assertThat(result.currency).isEqualTo("TEST") } @Test fun `when adding more orders than max cache size, then oldest orders should be removed`() = runTest { // GIVEN - val orders = (1..10005L).map { createTestOrder(it) } + val orders = (1..10005L).map { OrderTestUtils.generateTestOrder(it) } // WHEN cache.addAll(orders) @@ -94,13 +91,13 @@ class WooPosOrdersInMemoryCacheTest { @Test fun `when multiple threads access cache concurrently, then data remains consistent`() = runTest { // GIVEN - val initialOrders = (1..100L).map { createTestOrder(it) } + val initialOrders = (1..100L).map { OrderTestUtils.generateTestOrder(it) } cache.addAll(initialOrders) // WHEN - simulate concurrent access val concurrentOperations = (101..200L).map { id -> async { - val order = createTestOrder(id) + val order = OrderTestUtils.generateTestOrder(id) cache.addAll(listOf(order)) cache.getAll().find { it.id == id } } @@ -119,16 +116,4 @@ class WooPosOrdersInMemoryCacheTest { val expectedIds = (176L..200L).toList() assertThat(allCachedOrders.map { it.id }).containsExactlyInAnyOrderElementsOf(expectedIds) } - - private fun createTestOrder( - id: Long, - note: String = "Order $id" - ): Order = mock { - on { this.id }.thenReturn(id) - on { customerNote }.thenReturn(note) - on { dateCreated }.thenReturn(Date()) - on { total }.thenReturn(BigDecimal.TEN) - on { currency }.thenReturn("USD") - on { number }.thenReturn(id.toString()) - } } From e0e532532baa85f99b76404fbcd29195a0067e17 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ce=CC=81sar=20Vargas=20Casaseca?= Date: Fri, 5 Sep 2025 15:41:10 +0200 Subject: [PATCH 14/22] Improve POS Orders cache --- .../woocommerce/android/di/WooPosModule.kt | 7 +++---- .../common/data/WooPosOrdersInMemoryCache.kt | 19 +++++++++---------- .../searchbyidentifier/WooPosOrdersCache.kt | 15 --------------- .../woopos/orders/WooPosOrdersDataSource.kt | 9 ++++++--- .../android/ui/woopos/root/WooPosActivity.kt | 12 ------------ .../ui/woopos/splash/WooPosSplashViewModel.kt | 5 ++++- .../orders/WooPosOrdersDataSourceTest.kt | 4 ++-- 7 files changed, 24 insertions(+), 47 deletions(-) delete mode 100644 WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/common/data/searchbyidentifier/WooPosOrdersCache.kt diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/di/WooPosModule.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/di/WooPosModule.kt index cd4bdbfad5da..ab07e9b47fdd 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/di/WooPosModule.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/di/WooPosModule.kt @@ -3,7 +3,6 @@ package com.woocommerce.android.di import com.woocommerce.android.ui.woopos.common.data.WooPosOrdersInMemoryCache import com.woocommerce.android.ui.woopos.common.data.WooPosProductsCache import com.woocommerce.android.ui.woopos.common.data.WooPosProductsInMemoryCache -import com.woocommerce.android.ui.woopos.common.data.searchbyidentifier.WooPosOrdersCache import dagger.Binds import dagger.Module import dagger.hilt.InstallIn @@ -17,7 +16,7 @@ abstract class WooPosModule { @Singleton abstract fun provideWooPosProductsCache(implementation: WooPosProductsInMemoryCache): WooPosProductsCache - @Binds - @Singleton - abstract fun provideWooPosOrdersCache(implementation: WooPosOrdersInMemoryCache): WooPosOrdersCache +// @Binds +// @Singleton +// abstract fun provideWooPosOrdersCache(implementation: WooPosOrdersInMemoryCache): WooPosOrdersInMemoryCache } diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/common/data/WooPosOrdersInMemoryCache.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/common/data/WooPosOrdersInMemoryCache.kt index d820d6fae126..b7ce770415f9 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/common/data/WooPosOrdersInMemoryCache.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/common/data/WooPosOrdersInMemoryCache.kt @@ -1,39 +1,38 @@ package com.woocommerce.android.ui.woopos.common.data import com.woocommerce.android.model.Order -import com.woocommerce.android.ui.woopos.common.data.searchbyidentifier.WooPosOrdersCache -import com.woocommerce.android.ui.woopos.common.data.searchbyidentifier.WooPosOrdersCache.Companion.MAX_CACHE_SIZE +import com.woocommerce.android.ui.woopos.orders.WooPosOrdersDataSource.Companion.POS_ORDERS_PAGE_SIZE import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock import javax.inject.Inject -class WooPosOrdersInMemoryCache @Inject constructor() : WooPosOrdersCache { +class WooPosOrdersInMemoryCache @Inject constructor() { private val mutex = Mutex() companion object { - private const val INITIAL_CAPACITY = MAX_CACHE_SIZE + private const val CACHE_CAPACITY = POS_ORDERS_PAGE_SIZE private const val LOAD_FACTOR = 0.75f } - private val ordersCache = LinkedHashMap(INITIAL_CAPACITY, LOAD_FACTOR, true) + private val ordersCache = LinkedHashMap(CACHE_CAPACITY, LOAD_FACTOR, true) - override suspend fun addAll(orders: List) = mutex.withLock { + suspend fun addAll(orders: List) = mutex.withLock { addAllInternal(orders) } - override suspend fun getAll(): List = mutex.withLock { + suspend fun getAll(): List = mutex.withLock { ordersCache.values.toList() } - override suspend fun clear() = mutex.withLock { + suspend fun clear() = mutex.withLock { ordersCache.clear() } private fun addAllInternal(orders: List) { orders.forEach { order -> ordersCache[order.id] = order - if (ordersCache.size > MAX_CACHE_SIZE) { - val keysToRemove = ordersCache.keys.take(ordersCache.size - MAX_CACHE_SIZE) + if (ordersCache.size > CACHE_CAPACITY) { + val keysToRemove = ordersCache.keys.take(ordersCache.size - CACHE_CAPACITY) keysToRemove.forEach { ordersCache.remove(it) } } } diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/common/data/searchbyidentifier/WooPosOrdersCache.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/common/data/searchbyidentifier/WooPosOrdersCache.kt deleted file mode 100644 index 51addf331b14..000000000000 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/common/data/searchbyidentifier/WooPosOrdersCache.kt +++ /dev/null @@ -1,15 +0,0 @@ -package com.woocommerce.android.ui.woopos.common.data.searchbyidentifier - -import com.woocommerce.android.model.Order - -interface WooPosOrdersCache { - companion object { - const val MAX_CACHE_SIZE = 25 - } - - suspend fun addAll(orders: List) - - suspend fun getAll(): List - - suspend fun clear() -} diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/orders/WooPosOrdersDataSource.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/orders/WooPosOrdersDataSource.kt index 25f7f6c0fe51..bc50f17d967e 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/orders/WooPosOrdersDataSource.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/orders/WooPosOrdersDataSource.kt @@ -3,7 +3,7 @@ package com.woocommerce.android.ui.woopos.orders import com.woocommerce.android.model.Order import com.woocommerce.android.model.OrderMapper import com.woocommerce.android.tools.SelectedSite -import com.woocommerce.android.ui.woopos.common.data.searchbyidentifier.WooPosOrdersCache +import com.woocommerce.android.ui.woopos.common.data.WooPosOrdersInMemoryCache import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.flow import org.wordpress.android.fluxc.network.rest.wpcom.wc.order.OrderRestClient @@ -20,15 +20,18 @@ class WooPosOrdersDataSource @Inject constructor( private val restClient: OrderRestClient, private val selectedSite: SelectedSite, private val orderMapper: OrderMapper, - private val ordersCache: WooPosOrdersCache + private val ordersCache: WooPosOrdersInMemoryCache ) { + companion object { + const val POS_ORDERS_PAGE_SIZE = 25 + } fun loadOrders(): Flow = flow { val cached = ordersCache.getAll() emit(LoadOrdersResult.Success(cached)) val result = restClient.fetchOrders( site = selectedSite.get(), - count = 25, + count = POS_ORDERS_PAGE_SIZE, page = 1, orderBy = OrderBy.DATE, sortOrder = OrderRestClient.SortOrder.DESCENDING, diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/root/WooPosActivity.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/root/WooPosActivity.kt index ac32c3ccc3a7..615069cff3a8 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/root/WooPosActivity.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/root/WooPosActivity.kt @@ -14,13 +14,11 @@ import androidx.core.view.WindowInsetsCompat import com.woocommerce.android.ui.woopos.cardreader.WooPosCardReaderFacade import com.woocommerce.android.ui.woopos.common.composeui.designsystem.WooPosSpacing import com.woocommerce.android.ui.woopos.common.composeui.designsystem.WooPosTheme -import com.woocommerce.android.ui.woopos.common.data.searchbyidentifier.WooPosOrdersCache import com.woocommerce.android.ui.woopos.home.items.coupons.creation.WooPosCouponCreationFacade import com.woocommerce.android.ui.woopos.support.WooPosGetSupportFacade import com.woocommerce.android.ui.woopos.util.analytics.WooPosAnalyticsTracker import com.woocommerce.android.ui.woopos.util.ext.isGestureNavigation import dagger.hilt.android.AndroidEntryPoint -import kotlinx.coroutines.runBlocking import javax.inject.Inject @AndroidEntryPoint @@ -37,9 +35,6 @@ class WooPosActivity : AppCompatActivity() { @Inject lateinit var wooPosCouponCreationFacade: WooPosCouponCreationFacade - @Inject - lateinit var ordersCache: WooPosOrdersCache - override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) WindowCompat.setDecorFitsSystemWindows(window, false) @@ -58,13 +53,6 @@ class WooPosActivity : AppCompatActivity() { } } } - - override fun onDestroy() { - runBlocking { - ordersCache.clear() - } - super.onDestroy() - } } @Composable diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/splash/WooPosSplashViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/splash/WooPosSplashViewModel.kt index cc5ab787c2fc..8429950c8910 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/splash/WooPosSplashViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/splash/WooPosSplashViewModel.kt @@ -2,6 +2,7 @@ package com.woocommerce.android.ui.woopos.splash import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +import com.woocommerce.android.ui.woopos.common.data.WooPosOrdersInMemoryCache import com.woocommerce.android.ui.woopos.common.data.WooPosPopularProductsProvider import com.woocommerce.android.ui.woopos.home.items.products.WooPosProductsDataSource import com.woocommerce.android.ui.woopos.tab.WooPosCanBeLaunchedInTab @@ -22,6 +23,7 @@ class WooPosSplashViewModel @Inject constructor( private val popularProductsProvider: WooPosPopularProductsProvider, private val analyticsTracker: WooPosAnalyticsTracker, private val posCanBeLaunchedInTab: WooPosCanBeLaunchedInTab, + private val ordersCache: WooPosOrdersInMemoryCache ) : ViewModel() { private val _state = MutableStateFlow(WooPosSplashState.Loading) val state: StateFlow = _state @@ -38,7 +40,8 @@ class WooPosSplashViewModel @Inject constructor( joinAll( launch { productsDataSource.prepopulateProductsCache() }, - launch { popularProductsProvider.fetchAndCachePopularProducts() } + launch { popularProductsProvider.fetchAndCachePopularProducts() }, + launch { ordersCache.clear() } ) _state.value = WooPosSplashState.Loaded trackPosLoaded(splashScreenStartTime) diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/orders/WooPosOrdersDataSourceTest.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/orders/WooPosOrdersDataSourceTest.kt index 1e2c257c5f74..bc080e98fc1c 100644 --- a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/orders/WooPosOrdersDataSourceTest.kt +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/orders/WooPosOrdersDataSourceTest.kt @@ -3,7 +3,7 @@ package com.woocommerce.android.ui.woopos.orders import com.woocommerce.android.model.OrderMapper import com.woocommerce.android.tools.SelectedSite import com.woocommerce.android.ui.orders.OrderTestUtils -import com.woocommerce.android.ui.woopos.common.data.searchbyidentifier.WooPosOrdersCache +import com.woocommerce.android.ui.woopos.common.data.WooPosOrdersInMemoryCache import com.woocommerce.android.ui.woopos.util.WooPosCoroutineTestRule import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.toList @@ -36,7 +36,7 @@ class WooPosOrdersDataSourceTest { private val siteModel: SiteModel = mock() private val selectedSite: SelectedSite = mock { on { get() }.thenReturn(siteModel) } private val orderMapper: OrderMapper = mock() - private val ordersCache: WooPosOrdersCache = mock() + private val ordersCache: WooPosOrdersInMemoryCache = mock() private val sut = WooPosOrdersDataSource( restClient = orderRestClient, From 0cadb0c6e8ecade420774eb8c0583f0acdb06c4b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ce=CC=81sar=20Vargas=20Casaseca?= Date: Fri, 5 Sep 2025 15:42:15 +0200 Subject: [PATCH 15/22] Remove non necessary code --- .../main/kotlin/com/woocommerce/android/di/WooPosModule.kt | 5 ----- 1 file changed, 5 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/di/WooPosModule.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/di/WooPosModule.kt index ab07e9b47fdd..547c8079e25c 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/di/WooPosModule.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/di/WooPosModule.kt @@ -1,6 +1,5 @@ package com.woocommerce.android.di -import com.woocommerce.android.ui.woopos.common.data.WooPosOrdersInMemoryCache import com.woocommerce.android.ui.woopos.common.data.WooPosProductsCache import com.woocommerce.android.ui.woopos.common.data.WooPosProductsInMemoryCache import dagger.Binds @@ -15,8 +14,4 @@ abstract class WooPosModule { @Binds @Singleton abstract fun provideWooPosProductsCache(implementation: WooPosProductsInMemoryCache): WooPosProductsCache - -// @Binds -// @Singleton -// abstract fun provideWooPosOrdersCache(implementation: WooPosOrdersInMemoryCache): WooPosOrdersInMemoryCache } From fe798f603252b38d4e7da2185fad43c49c416c0d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ce=CC=81sar=20Vargas=20Casaseca?= Date: Fri, 5 Sep 2025 15:43:17 +0200 Subject: [PATCH 16/22] Move file to its right package --- .../android/ui/woopos/orders/WooPosOrdersDataSource.kt | 2 +- .../{common/data => orders}/WooPosOrdersInMemoryCache.kt | 5 ++--- .../android/ui/woopos/splash/WooPosSplashViewModel.kt | 2 +- .../ui/woopos/common/data/WooPosOrdersInMemoryCacheTest.kt | 1 + .../android/ui/woopos/orders/WooPosOrdersDataSourceTest.kt | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) rename WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/{common/data => orders}/WooPosOrdersInMemoryCache.kt (82%) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/orders/WooPosOrdersDataSource.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/orders/WooPosOrdersDataSource.kt index bc50f17d967e..b4df0fcb2f68 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/orders/WooPosOrdersDataSource.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/orders/WooPosOrdersDataSource.kt @@ -3,7 +3,7 @@ package com.woocommerce.android.ui.woopos.orders import com.woocommerce.android.model.Order import com.woocommerce.android.model.OrderMapper import com.woocommerce.android.tools.SelectedSite -import com.woocommerce.android.ui.woopos.common.data.WooPosOrdersInMemoryCache +import com.woocommerce.android.ui.woopos.orders.WooPosOrdersInMemoryCache import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.flow import org.wordpress.android.fluxc.network.rest.wpcom.wc.order.OrderRestClient diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/common/data/WooPosOrdersInMemoryCache.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/orders/WooPosOrdersInMemoryCache.kt similarity index 82% rename from WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/common/data/WooPosOrdersInMemoryCache.kt rename to WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/orders/WooPosOrdersInMemoryCache.kt index b7ce770415f9..f2e8ecf47e0d 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/common/data/WooPosOrdersInMemoryCache.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/orders/WooPosOrdersInMemoryCache.kt @@ -1,7 +1,6 @@ -package com.woocommerce.android.ui.woopos.common.data +package com.woocommerce.android.ui.woopos.orders import com.woocommerce.android.model.Order -import com.woocommerce.android.ui.woopos.orders.WooPosOrdersDataSource.Companion.POS_ORDERS_PAGE_SIZE import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock import javax.inject.Inject @@ -10,7 +9,7 @@ class WooPosOrdersInMemoryCache @Inject constructor() { private val mutex = Mutex() companion object { - private const val CACHE_CAPACITY = POS_ORDERS_PAGE_SIZE + private const val CACHE_CAPACITY = WooPosOrdersDataSource.Companion.POS_ORDERS_PAGE_SIZE private const val LOAD_FACTOR = 0.75f } diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/splash/WooPosSplashViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/splash/WooPosSplashViewModel.kt index 8429950c8910..191bce08d575 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/splash/WooPosSplashViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/splash/WooPosSplashViewModel.kt @@ -2,7 +2,7 @@ package com.woocommerce.android.ui.woopos.splash import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope -import com.woocommerce.android.ui.woopos.common.data.WooPosOrdersInMemoryCache +import com.woocommerce.android.ui.woopos.orders.WooPosOrdersInMemoryCache import com.woocommerce.android.ui.woopos.common.data.WooPosPopularProductsProvider import com.woocommerce.android.ui.woopos.home.items.products.WooPosProductsDataSource import com.woocommerce.android.ui.woopos.tab.WooPosCanBeLaunchedInTab diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/common/data/WooPosOrdersInMemoryCacheTest.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/common/data/WooPosOrdersInMemoryCacheTest.kt index a1c291347157..eb70f0d59724 100644 --- a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/common/data/WooPosOrdersInMemoryCacheTest.kt +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/common/data/WooPosOrdersInMemoryCacheTest.kt @@ -1,6 +1,7 @@ package com.woocommerce.android.ui.woopos.common.data import com.woocommerce.android.ui.orders.OrderTestUtils +import com.woocommerce.android.ui.woopos.orders.WooPosOrdersInMemoryCache import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.async import kotlinx.coroutines.awaitAll diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/orders/WooPosOrdersDataSourceTest.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/orders/WooPosOrdersDataSourceTest.kt index bc080e98fc1c..4b9b280ca454 100644 --- a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/orders/WooPosOrdersDataSourceTest.kt +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/orders/WooPosOrdersDataSourceTest.kt @@ -3,7 +3,7 @@ package com.woocommerce.android.ui.woopos.orders import com.woocommerce.android.model.OrderMapper import com.woocommerce.android.tools.SelectedSite import com.woocommerce.android.ui.orders.OrderTestUtils -import com.woocommerce.android.ui.woopos.common.data.WooPosOrdersInMemoryCache +import com.woocommerce.android.ui.woopos.orders.WooPosOrdersInMemoryCache import com.woocommerce.android.ui.woopos.util.WooPosCoroutineTestRule import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.toList From 10c135cd69d504b2989f51dbc93d37737e035021 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ce=CC=81sar=20Vargas=20Casaseca?= Date: Fri, 5 Sep 2025 15:46:21 +0200 Subject: [PATCH 17/22] Revert change --- .../android/ui/woopos/orders/WooPosOrdersDataSource.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/orders/WooPosOrdersDataSource.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/orders/WooPosOrdersDataSource.kt index b4df0fcb2f68..c74d1d9e5fbf 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/orders/WooPosOrdersDataSource.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/orders/WooPosOrdersDataSource.kt @@ -48,7 +48,7 @@ class WooPosOrdersDataSource @Inject constructor( } } - private suspend fun List?.toAppModels(): List = this?.map { + private suspend fun List.toAppModels(): List = map { orderMapper.toAppModel(it) } ?: emptyList() } From eab3bc73edb35cd8e134dd0438b25e36804a1c5b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ce=CC=81sar=20Vargas=20Casaseca?= Date: Fri, 5 Sep 2025 15:52:18 +0200 Subject: [PATCH 18/22] Add unit test for clearing cache on splash screen --- .../ui/woopos/splash/WooPosSplashViewModelTest.kt | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/splash/WooPosSplashViewModelTest.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/splash/WooPosSplashViewModelTest.kt index a214fd91ab3a..e95c7c3c95b4 100644 --- a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/splash/WooPosSplashViewModelTest.kt +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/splash/WooPosSplashViewModelTest.kt @@ -2,6 +2,7 @@ package com.woocommerce.android.ui.woopos.splash import com.woocommerce.android.ui.woopos.common.data.WooPosPopularProductsProvider import com.woocommerce.android.ui.woopos.home.items.products.WooPosProductsDataSource +import com.woocommerce.android.ui.woopos.orders.WooPosOrdersInMemoryCache import com.woocommerce.android.ui.woopos.tab.WooPosCanBeLaunchedInTab import com.woocommerce.android.ui.woopos.tab.WooPosLaunchability import com.woocommerce.android.ui.woopos.util.WooPosCoroutineTestRule @@ -23,6 +24,7 @@ import kotlin.test.Test @ExperimentalCoroutinesApi class WooPosSplashViewModelTest { private val productsDataSource: WooPosProductsDataSource = mock() + private val ordersCache: WooPosOrdersInMemoryCache = mock() private val analyticsTracker: WooPosAnalyticsTracker = mock() private val popularProductsProvider: WooPosPopularProductsProvider = mock() private val posCanBeLaunchedInTab: WooPosCanBeLaunchedInTab = mock() @@ -109,6 +111,15 @@ class WooPosSplashViewModelTest { verify(popularProductsProvider).fetchAndCachePopularProducts() } + @Test + fun `when sut is created, should clear orders cache`() = runTest { + // WHEN + createSut() + + // THEN + verify(ordersCache).clear() + } + @Test fun `given product population fails, should still update state to Loaded`() = runTest { // GIVEN @@ -145,6 +156,7 @@ class WooPosSplashViewModelTest { productsDataSource, popularProductsProvider, analyticsTracker, - posCanBeLaunchedInTab + posCanBeLaunchedInTab, + ordersCache ) } From 1d9175158a81f0dcdc107c7b23aaf0e08c2bdc2f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ce=CC=81sar=20Vargas=20Casaseca?= Date: Fri, 5 Sep 2025 16:07:46 +0200 Subject: [PATCH 19/22] Add test --- .../android/ui/woopos/splash/WooPosSplashViewModelTest.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/splash/WooPosSplashViewModelTest.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/splash/WooPosSplashViewModelTest.kt index e95c7c3c95b4..b148a3772830 100644 --- a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/splash/WooPosSplashViewModelTest.kt +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/splash/WooPosSplashViewModelTest.kt @@ -112,7 +112,7 @@ class WooPosSplashViewModelTest { } @Test - fun `when sut is created, should clear orders cache`() = runTest { + fun `when sut is created, then should clear orders cache`() = runTest { // WHEN createSut() From 02fa76f4abed96f2144951ec20137749d5004d60 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ce=CC=81sar=20Vargas=20Casaseca?= Date: Fri, 5 Sep 2025 16:08:08 +0200 Subject: [PATCH 20/22] Simplify cache --- .../woopos/orders/WooPosOrdersDataSource.kt | 3 +- .../orders/WooPosOrdersInMemoryCache.kt | 34 ++----- .../ui/woopos/splash/WooPosSplashViewModel.kt | 2 +- .../data/WooPosOrdersInMemoryCacheTest.kt | 97 ++++++------------- .../orders/WooPosOrdersDataSourceTest.kt | 7 +- 5 files changed, 42 insertions(+), 101 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/orders/WooPosOrdersDataSource.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/orders/WooPosOrdersDataSource.kt index c74d1d9e5fbf..6bbe44f985f0 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/orders/WooPosOrdersDataSource.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/orders/WooPosOrdersDataSource.kt @@ -3,7 +3,6 @@ package com.woocommerce.android.ui.woopos.orders import com.woocommerce.android.model.Order import com.woocommerce.android.model.OrderMapper import com.woocommerce.android.tools.SelectedSite -import com.woocommerce.android.ui.woopos.orders.WooPosOrdersInMemoryCache import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.flow import org.wordpress.android.fluxc.network.rest.wpcom.wc.order.OrderRestClient @@ -43,7 +42,7 @@ class WooPosOrdersDataSource @Inject constructor( emit(LoadOrdersResult.Error(result.error.message)) } else { val mapped = result.orders.toAppModels() - ordersCache.addAll(mapped) + ordersCache.setAll(mapped) emit(LoadOrdersResult.Success(result.orders.toAppModels())) } } diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/orders/WooPosOrdersInMemoryCache.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/orders/WooPosOrdersInMemoryCache.kt index f2e8ecf47e0d..190782ca2afa 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/orders/WooPosOrdersInMemoryCache.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/orders/WooPosOrdersInMemoryCache.kt @@ -1,39 +1,19 @@ package com.woocommerce.android.ui.woopos.orders import com.woocommerce.android.model.Order -import kotlinx.coroutines.sync.Mutex -import kotlinx.coroutines.sync.withLock +import java.util.concurrent.atomic.AtomicReference import javax.inject.Inject class WooPosOrdersInMemoryCache @Inject constructor() { - private val mutex = Mutex() + private val ordersCache = AtomicReference>(emptyList()) - companion object { - private const val CACHE_CAPACITY = WooPosOrdersDataSource.Companion.POS_ORDERS_PAGE_SIZE - private const val LOAD_FACTOR = 0.75f + fun setAll(orders: List) { + ordersCache.set(orders.toList()) } - private val ordersCache = LinkedHashMap(CACHE_CAPACITY, LOAD_FACTOR, true) + fun getAll(): List = ordersCache.get() - suspend fun addAll(orders: List) = mutex.withLock { - addAllInternal(orders) - } - - suspend fun getAll(): List = mutex.withLock { - ordersCache.values.toList() - } - - suspend fun clear() = mutex.withLock { - ordersCache.clear() - } - - private fun addAllInternal(orders: List) { - orders.forEach { order -> - ordersCache[order.id] = order - if (ordersCache.size > CACHE_CAPACITY) { - val keysToRemove = ordersCache.keys.take(ordersCache.size - CACHE_CAPACITY) - keysToRemove.forEach { ordersCache.remove(it) } - } - } + fun clear() { + ordersCache.set(emptyList()) } } diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/splash/WooPosSplashViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/splash/WooPosSplashViewModel.kt index 191bce08d575..a23c099e7e8f 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/splash/WooPosSplashViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/splash/WooPosSplashViewModel.kt @@ -2,9 +2,9 @@ package com.woocommerce.android.ui.woopos.splash import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope -import com.woocommerce.android.ui.woopos.orders.WooPosOrdersInMemoryCache import com.woocommerce.android.ui.woopos.common.data.WooPosPopularProductsProvider import com.woocommerce.android.ui.woopos.home.items.products.WooPosProductsDataSource +import com.woocommerce.android.ui.woopos.orders.WooPosOrdersInMemoryCache import com.woocommerce.android.ui.woopos.tab.WooPosCanBeLaunchedInTab import com.woocommerce.android.ui.woopos.tab.WooPosLaunchability import com.woocommerce.android.ui.woopos.util.analytics.WooPosAnalyticsEvent.Event.Loaded diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/common/data/WooPosOrdersInMemoryCacheTest.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/common/data/WooPosOrdersInMemoryCacheTest.kt index eb70f0d59724..c7956970f92d 100644 --- a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/common/data/WooPosOrdersInMemoryCacheTest.kt +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/common/data/WooPosOrdersInMemoryCacheTest.kt @@ -3,9 +3,6 @@ package com.woocommerce.android.ui.woopos.common.data import com.woocommerce.android.ui.orders.OrderTestUtils import com.woocommerce.android.ui.woopos.orders.WooPosOrdersInMemoryCache import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.async -import kotlinx.coroutines.awaitAll -import kotlinx.coroutines.test.runTest import org.assertj.core.api.Assertions.assertThat import org.junit.Before import org.junit.Test @@ -21,7 +18,7 @@ class WooPosOrdersInMemoryCacheTest { } @Test - fun `when cache is empty, then getAll returns empty list`() = runTest { + fun `when cache is empty, then getAll returns empty list`() { // WHEN val result = cache.getAll() @@ -30,91 +27,57 @@ class WooPosOrdersInMemoryCacheTest { } @Test - fun `when orders are added, then getAll returns all orders`() = runTest { + fun `when setAll is called, then getAll returns the same elements`() { // GIVEN - val orders = listOf(OrderTestUtils.generateTestOrder(1), OrderTestUtils.generateTestOrder(2)) + val orders = listOf( + OrderTestUtils.generateTestOrder(1), + OrderTestUtils.generateTestOrder(2) + ) // WHEN - cache.addAll(orders) + cache.setAll(orders) val result = cache.getAll() // THEN - assertThat(result).hasSize(2) - assertThat(result).containsExactlyInAnyOrderElementsOf(orders) + assertThat(result).containsExactlyElementsOf(orders) } @Test - fun `when cache is cleared, then getAll returns empty list`() = runTest { + fun `when setAll is called twice, then last write wins`() { // GIVEN - val orders = listOf(OrderTestUtils.generateTestOrder(1), OrderTestUtils.generateTestOrder(2)) - cache.addAll(orders) + val first = listOf( + OrderTestUtils.generateTestOrder(1), + OrderTestUtils.generateTestOrder(2) + ) + val second = listOf( + OrderTestUtils.generateTestOrder(3), + OrderTestUtils.generateTestOrder(4), + OrderTestUtils.generateTestOrder(5) + ) // WHEN - cache.clear() + cache.setAll(first) + cache.setAll(second) val result = cache.getAll() // THEN - assertThat(result).isEmpty() + assertThat(result).containsExactlyElementsOf(second) } @Test - fun `when adding order with existing id, then it should replace old order`() = runTest { + fun `when cache is cleared, then getAll returns empty list`() { // GIVEN - val order1 = OrderTestUtils.generateTestOrder(1) - val order1Updated = OrderTestUtils.generateTestOrder(1).copy(currency = "TEST") + val orders = listOf( + OrderTestUtils.generateTestOrder(1), + OrderTestUtils.generateTestOrder(2) + ) + cache.setAll(orders) // WHEN - cache.addAll(listOf(order1)) - cache.addAll(listOf(order1Updated)) - val result = cache.getAll().first { it.id == 1L } - - // THEN - assertThat(result).isEqualTo(order1Updated) - assertThat(result.currency).isEqualTo("TEST") - } - - @Test - fun `when adding more orders than max cache size, then oldest orders should be removed`() = runTest { - // GIVEN - val orders = (1..10005L).map { OrderTestUtils.generateTestOrder(it) } - - // WHEN - cache.addAll(orders) - val firstOrder = cache.getAll().find { it.id == 1L } - val lastOrder = cache.getAll().find { it.id == 10005L } - - // THEN - assertThat(firstOrder).isNull() - assertThat(lastOrder).isNotNull - assertThat(cache.getAll()).hasSize(25) - } - - @Test - fun `when multiple threads access cache concurrently, then data remains consistent`() = runTest { - // GIVEN - val initialOrders = (1..100L).map { OrderTestUtils.generateTestOrder(it) } - cache.addAll(initialOrders) - - // WHEN - simulate concurrent access - val concurrentOperations = (101..200L).map { id -> - async { - val order = OrderTestUtils.generateTestOrder(id) - cache.addAll(listOf(order)) - cache.getAll().find { it.id == id } - } - } + cache.clear() + val result = cache.getAll() // THEN - val results = concurrentOperations.awaitAll() - assertThat(results).hasSize(100) - assertThat(results).doesNotContainNull() - - val allCachedOrders = cache.getAll() - // Only MAX_CACHE_SIZE should remain - assertThat(allCachedOrders).hasSize(25) - - // The most recent 25 orders should be present - val expectedIds = (176L..200L).toList() - assertThat(allCachedOrders.map { it.id }).containsExactlyInAnyOrderElementsOf(expectedIds) + assertThat(result).isEmpty() } } diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/orders/WooPosOrdersDataSourceTest.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/orders/WooPosOrdersDataSourceTest.kt index 4b9b280ca454..8e30dbe90fa9 100644 --- a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/orders/WooPosOrdersDataSourceTest.kt +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/orders/WooPosOrdersDataSourceTest.kt @@ -3,7 +3,6 @@ package com.woocommerce.android.ui.woopos.orders import com.woocommerce.android.model.OrderMapper import com.woocommerce.android.tools.SelectedSite import com.woocommerce.android.ui.orders.OrderTestUtils -import com.woocommerce.android.ui.woopos.orders.WooPosOrdersInMemoryCache import com.woocommerce.android.ui.woopos.util.WooPosCoroutineTestRule import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.toList @@ -99,7 +98,7 @@ class WooPosOrdersDataSourceTest { verify(selectedSite).get() verify(ordersCache).getAll() - verify(ordersCache).addAll(listOf(firstOrder, secondOrder)) + verify(ordersCache).setAll(listOf(firstOrder, secondOrder)) verify(orderRestClient).fetchOrders( site = eq(siteModel), count = eq(25), @@ -154,7 +153,7 @@ class WooPosOrdersDataSourceTest { assertThat((second as LoadOrdersResult.Error).message).isEqualTo("generic error") verify(ordersCache).getAll() - verify(ordersCache, never()).addAll(any()) + verify(ordersCache, never()).setAll(any()) verify(orderRestClient).fetchOrders( site = eq(siteModel), count = eq(25), @@ -203,7 +202,7 @@ class WooPosOrdersDataSourceTest { verify(selectedSite).get() verify(ordersCache).getAll() - verify(ordersCache).addAll(emptyList()) + verify(ordersCache).setAll(emptyList()) verify(orderRestClient).fetchOrders( site = eq(siteModel), count = eq(25), From a57941cfdc18f88fdf02075d22e554d71eeea069 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ce=CC=81sar=20Vargas=20Casaseca?= Date: Fri, 5 Sep 2025 16:21:55 +0200 Subject: [PATCH 21/22] Make cache a singleton --- .../android/ui/woopos/orders/WooPosOrdersInMemoryCache.kt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/orders/WooPosOrdersInMemoryCache.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/orders/WooPosOrdersInMemoryCache.kt index 190782ca2afa..cfda1cb0b832 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/orders/WooPosOrdersInMemoryCache.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/orders/WooPosOrdersInMemoryCache.kt @@ -3,7 +3,9 @@ package com.woocommerce.android.ui.woopos.orders import com.woocommerce.android.model.Order import java.util.concurrent.atomic.AtomicReference import javax.inject.Inject +import javax.inject.Singleton +@Singleton class WooPosOrdersInMemoryCache @Inject constructor() { private val ordersCache = AtomicReference>(emptyList()) From 9720e3c6665b36365b42c4c162f17c2b5b0bca0f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ce=CC=81sar=20Vargas=20Casaseca?= Date: Fri, 5 Sep 2025 16:23:38 +0200 Subject: [PATCH 22/22] Fallback string not necessary --- .../android/ui/woopos/orders/WooPosOrdersViewModel.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/orders/WooPosOrdersViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/orders/WooPosOrdersViewModel.kt index 5979d290597e..5a8d12c7bf3f 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/orders/WooPosOrdersViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/orders/WooPosOrdersViewModel.kt @@ -36,7 +36,7 @@ class WooPosOrdersViewModel @Inject constructor( _state.update { it.copy( isLoading = false, - error = result.message ?: "Unknown error" + error = result.message ) } }