Skip to content
This repository was archived by the owner on Feb 4, 2025. It is now read-only.

Commit 090737c

Browse files
authored
Merge pull request #2959 from wordpress-mobile/issue/woo-7166-auto-update-order-list
Refresh ListStore component on database updates
2 parents d4257a8 + fa5de72 commit 090737c

File tree

9 files changed

+241
-107
lines changed

9 files changed

+241
-107
lines changed

example/src/test/java/org/wordpress/android/fluxc/wc/order/WCOrderStoreTest.kt

Lines changed: 27 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ import org.wordpress.android.fluxc.persistence.WCAndroidDatabase
4242
import org.wordpress.android.fluxc.persistence.WellSqlConfig
4343
import org.wordpress.android.fluxc.persistence.dao.OrderMetaDataDao
4444
import org.wordpress.android.fluxc.persistence.dao.OrderNotesDao
45-
import org.wordpress.android.fluxc.persistence.dao.OrdersDao
45+
import org.wordpress.android.fluxc.persistence.dao.OrdersDaoDecorator
4646
import org.wordpress.android.fluxc.store.InsertOrder
4747
import org.wordpress.android.fluxc.store.WCOrderFetcher
4848
import org.wordpress.android.fluxc.store.WCOrderStore
@@ -65,7 +65,7 @@ import kotlin.test.assertTrue
6565
class WCOrderStoreTest {
6666
private val orderFetcher: WCOrderFetcher = mock()
6767
private val orderRestClient: OrderRestClient = mock()
68-
lateinit var ordersDao: OrdersDao
68+
lateinit var ordersDaoDecorator: OrdersDaoDecorator
6969
lateinit var orderNotesDao: OrderNotesDao
7070
lateinit var orderMetaDataDao: OrderMetaDataDao
7171
lateinit var orderStore: WCOrderStore
@@ -79,16 +79,17 @@ class WCOrderStoreTest {
7979
.allowMainThreadQueries()
8080
.build()
8181

82-
ordersDao = database.ordersDao
82+
val dispatcher = Dispatcher()
83+
ordersDaoDecorator = OrdersDaoDecorator(dispatcher, database.ordersDao)
8384
orderNotesDao = database.orderNotesDao
8485
orderMetaDataDao = database.orderMetaDataDao
8586

8687
orderStore = WCOrderStore(
87-
dispatcher = Dispatcher(),
88+
dispatcher = dispatcher,
8889
wcOrderRestClient = orderRestClient,
8990
wcOrderFetcher = orderFetcher,
9091
coroutineEngine = initCoroutineEngine(),
91-
ordersDao = ordersDao,
92+
ordersDaoDecorator = ordersDaoDecorator,
9293
orderNotesDao = orderNotesDao,
9394
orderMetaDataDao = orderMetaDataDao,
9495
insertOrder = insertOrder
@@ -107,10 +108,10 @@ class WCOrderStoreTest {
107108
fun testSimpleInsertionAndRetrieval() {
108109
runBlocking {
109110
val orderModel = OrderTestUtils.generateSampleOrder(42)
110-
ordersDao.insertOrUpdateOrder(orderModel)
111+
ordersDaoDecorator.insertOrUpdateOrder(orderModel)
111112
val site = SiteModel().apply { id = orderModel.localSiteId.value }
112113

113-
val storedOrders = ordersDao.getOrdersForSite(site.localId())
114+
val storedOrders = ordersDaoDecorator.getOrdersForSite(site.localId())
114115
assertEquals(1, storedOrders.size)
115116
assertEquals(42, storedOrders[0].orderId)
116117
assertEquals(orderModel, storedOrders[0])
@@ -138,16 +139,22 @@ class WCOrderStoreTest {
138139
}
139140
}
140141

141-
private fun OrderEntity.saveToDb(): OrderEntity {
142-
ordersDao.insertOrUpdateOrder(this)
142+
private suspend fun OrderEntity.saveToDb(): OrderEntity {
143+
ordersDaoDecorator.insertOrUpdateOrder(this)
143144
return copy()
144145
}
145146

147+
private fun insertOrUpdate(item: OrderEntity) {
148+
runBlocking {
149+
ordersDaoDecorator.insertOrUpdateOrder(item)
150+
}
151+
}
152+
146153
@Test
147154
fun testGetOrderByLocalId() {
148155
runBlocking {
149156
val sampleOrder = OrderTestUtils.generateSampleOrder(3)
150-
ordersDao.insertOrUpdateOrder(sampleOrder)
157+
ordersDaoDecorator.insertOrUpdateOrder(sampleOrder)
151158

152159
val site = SiteModel().apply { this.id = sampleOrder.localSiteId.value }
153160

@@ -164,7 +171,7 @@ class WCOrderStoreTest {
164171
runBlocking {
165172
val customStatus = "chronologically-incongruous"
166173
val customStatusOrder = OrderTestUtils.generateSampleOrder(3, customStatus)
167-
ordersDao.insertOrUpdateOrder(customStatusOrder)
174+
ordersDaoDecorator.insertOrUpdateOrder(customStatusOrder)
168175

169176
val site = SiteModel().apply { id = customStatusOrder.localSiteId.value }
170177

@@ -184,7 +191,7 @@ class WCOrderStoreTest {
184191
@Test
185192
fun testUpdateOrderStatus() = runBlocking {
186193
val orderModel = OrderTestUtils.generateSampleOrder(42)
187-
ordersDao.insertOrUpdateOrder(orderModel)
194+
ordersDaoDecorator.insertOrUpdateOrder(orderModel)
188195
val site = SiteModel().apply { id = orderModel.localSiteId.value }
189196
val result = RemoteOrderPayload.Updating(orderModel.copy(status = CoreOrderStatus.REFUNDED.value), site)
190197
whenever(orderRestClient.updateOrderStatus(orderModel, site, CoreOrderStatus.REFUNDED.value))
@@ -284,15 +291,15 @@ class WCOrderStoreTest {
284291
val site = SiteModel().apply { id = 8 }
285292

286293
val upToDate = setupUpToDateOrders(site)
287-
upToDate.orders.filterNotNull().forEach(ordersDao::insertOrUpdateOrder)
288-
assertThat(ordersDao.getOrdersForSite(site.localId())).hasSize(10)
294+
upToDate.orders.filterNotNull().forEach(::insertOrUpdate)
295+
assertThat(ordersDaoDecorator.getOrdersForSite(site.localId())).hasSize(10)
289296

290297
val outdated = setupOutdatedOrders(site)
291-
outdated.orders.filterNotNull().forEach(ordersDao::insertOrUpdateOrder)
292-
assertThat(ordersDao.getOrdersForSite(site.localId())).hasSize(20)
298+
outdated.orders.filterNotNull().forEach(::insertOrUpdate)
299+
assertThat(ordersDaoDecorator.getOrdersForSite(site.localId())).hasSize(20)
293300

294301
val missing = setupMissingOrders()
295-
assertThat(ordersDao.getOrdersForSite(site.localId())).hasSize(20)
302+
assertThat(ordersDaoDecorator.getOrdersForSite(site.localId())).hasSize(20)
296303

297304
orderStore.onAction(
298305
newFetchedOrderListAction(
@@ -321,7 +328,7 @@ class WCOrderStoreTest {
321328
whenever(orderRestClient.updateOrderStatus(orderModel, site, CoreOrderStatus.COMPLETED.value))
322329
.thenReturn(result)
323330

324-
assertThat(ordersDao.getOrder(orderModel.orderId, orderModel.localSiteId)?.status)
331+
assertThat(ordersDaoDecorator.getOrder(orderModel.orderId, orderModel.localSiteId)?.status)
325332
.isEqualTo(CoreOrderStatus.PROCESSING.value)
326333

327334
orderStore.updateOrderStatus(
@@ -330,7 +337,7 @@ class WCOrderStoreTest {
330337
WCOrderStatusModel(CoreOrderStatus.COMPLETED.value)
331338
).toList()
332339

333-
assertThat(ordersDao.getOrder(orderModel.orderId, orderModel.localSiteId)?.status)
340+
assertThat(ordersDaoDecorator.getOrder(orderModel.orderId, orderModel.localSiteId)?.status)
334341
.isEqualTo(CoreOrderStatus.COMPLETED.value)
335342
Unit
336343
}
@@ -358,7 +365,7 @@ class WCOrderStoreTest {
358365
// Ensure the error is sent in the response
359366
assertThat(response.event.error).isEqualTo(error)
360367

361-
assertThat(ordersDao.getOrder(orderModel.orderId, orderModel.localSiteId)?.status)
368+
assertThat(ordersDaoDecorator.getOrder(orderModel.orderId, orderModel.localSiteId)?.status)
362369
.isEqualTo(CoreOrderStatus.PROCESSING.value)
363370
Unit
364371
}

plugins/woocommerce/src/main/kotlin/org/wordpress/android/fluxc/persistence/dao/OrdersDao.kt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,11 @@ import org.wordpress.android.fluxc.model.LocalOrRemoteId.LocalId
1010
import org.wordpress.android.fluxc.model.OrderEntity
1111
import org.wordpress.android.fluxc.network.rest.wpcom.wc.order.CoreOrderStatus
1212

13+
/**
14+
* ⚠️AVOID USING THIS CLASS DIRECTLY -
15+
* Use [OrdersDaoDecorator] to ensure the [org.wordpress.android.fluxc.store.ListStore] component
16+
* keeps receiving events.
17+
*/
1318
@Dao
1419
abstract class OrdersDao {
1520
@Query("SELECT * FROM OrderEntity")
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
package org.wordpress.android.fluxc.persistence.dao
2+
3+
import kotlinx.coroutines.flow.Flow
4+
import org.wordpress.android.fluxc.Dispatcher
5+
import org.wordpress.android.fluxc.generated.ListActionBuilder
6+
import org.wordpress.android.fluxc.model.LocalOrRemoteId
7+
import org.wordpress.android.fluxc.model.OrderEntity
8+
import org.wordpress.android.fluxc.model.WCOrderListDescriptor
9+
import org.wordpress.android.fluxc.network.rest.wpcom.wc.order.CoreOrderStatus
10+
import javax.inject.Inject
11+
import javax.inject.Singleton
12+
13+
@Singleton
14+
class OrdersDaoDecorator @Inject constructor(
15+
private val dispatcher: Dispatcher,
16+
private val ordersDao: OrdersDao,
17+
) {
18+
suspend fun updateLocalOrder(
19+
orderId: Long,
20+
localSiteId: LocalOrRemoteId.LocalId,
21+
updateOrder: OrderEntity.() -> OrderEntity
22+
) {
23+
getOrder(orderId, localSiteId)
24+
?.let(updateOrder)
25+
?.let { insertOrUpdateOrder(it) }
26+
}
27+
28+
@Suppress("unused")
29+
suspend fun getAllOrders(): List<OrderEntity> = ordersDao.getAllOrders()
30+
31+
/**
32+
* @param suppressListRefresh Suppresses emit of ListRequiresRefresh event. Can be used
33+
* when this method is invoked in a loop and the app needs to emit the event at the end.
34+
*/
35+
suspend fun insertOrUpdateOrder(order: OrderEntity, suppressListRefresh: Boolean = false) {
36+
val orderBeforeUpdate = ordersDao.getOrder(order.orderId, order.localSiteId)
37+
ordersDao.insertOrUpdateOrder(order)
38+
39+
if(!suppressListRefresh) {
40+
// Draft orders are not returned from the API. We need to re-fetch order list from the API
41+
// when the order is new or its status changed from draft to another status.
42+
val orderIsNewOrMovingFromDraft = orderBeforeUpdate == null
43+
|| (order.status != "auto-draft" && orderBeforeUpdate.status == "auto-draft")
44+
if (orderIsNewOrMovingFromDraft) {
45+
// Re-fetch order list
46+
emitRefreshListEvent(order.localSiteId)
47+
} else {
48+
// Re-load order list from local db
49+
emitInvalidateListEvent(order.localSiteId)
50+
}
51+
}
52+
}
53+
54+
suspend fun getOrder(orderId: Long, localSiteId: LocalOrRemoteId.LocalId): OrderEntity? =
55+
ordersDao.getOrder(orderId, localSiteId)
56+
57+
@Suppress("unused")
58+
fun observeOrder(orderId: Long, localSiteId: LocalOrRemoteId.LocalId): Flow<OrderEntity?> =
59+
ordersDao.observeOrder(orderId, localSiteId)
60+
61+
suspend fun getPaidOrdersForSiteDesc(
62+
localSiteId: LocalOrRemoteId.LocalId,
63+
status: List<String> = listOf(CoreOrderStatus.COMPLETED.value)
64+
): List<OrderEntity> = ordersDao.getPaidOrdersForSiteDesc(localSiteId, status)
65+
66+
suspend fun getOrdersForSite(
67+
localSiteId: LocalOrRemoteId.LocalId, status: List<String>
68+
): List<OrderEntity> = ordersDao.getOrdersForSite(localSiteId, status)
69+
70+
suspend fun getOrdersForSite(localSiteId: LocalOrRemoteId.LocalId): List<OrderEntity> =
71+
ordersDao.getOrdersForSite(localSiteId)
72+
73+
fun observeOrdersForSite(localSiteId: LocalOrRemoteId.LocalId): Flow<List<OrderEntity>> =
74+
ordersDao.observeOrdersForSite(localSiteId)
75+
76+
fun observeOrdersForSite(
77+
localSiteId: LocalOrRemoteId.LocalId, status: List<String>
78+
): Flow<List<OrderEntity>> = ordersDao.observeOrdersForSite(localSiteId, status)
79+
80+
fun getOrdersForSiteByRemoteIds(
81+
localSiteId: LocalOrRemoteId.LocalId, orderIds: List<Long>
82+
): List<OrderEntity> = ordersDao.getOrdersForSiteByRemoteIds(localSiteId, orderIds)
83+
84+
fun deleteOrdersForSite(localSiteId: LocalOrRemoteId.LocalId) {
85+
ordersDao.deleteOrdersForSite(localSiteId)
86+
emitInvalidateListEvent(localSiteId)
87+
}
88+
89+
fun getOrderCountForSite(localSiteId: LocalOrRemoteId.LocalId): Int =
90+
ordersDao.getOrderCountForSite(localSiteId)
91+
92+
fun observeOrderCountForSite(localSiteId: LocalOrRemoteId.LocalId, status: List<String>): Flow<Int> =
93+
ordersDao.observeOrderCountForSite(localSiteId, status)
94+
95+
suspend fun deleteOrder(localSiteId: LocalOrRemoteId.LocalId, orderId: Long) {
96+
ordersDao.deleteOrder(localSiteId, orderId)
97+
emitInvalidateListEvent(localSiteId)
98+
}
99+
100+
/**
101+
* Emit DataInvalidated event - the ListStore component reloads the data from the DB.
102+
*/
103+
private fun emitInvalidateListEvent(localSiteId: LocalOrRemoteId.LocalId) {
104+
val listTypeIdentifier = WCOrderListDescriptor.calculateTypeIdentifier(
105+
localSiteId = localSiteId.value
106+
)
107+
dispatcher.dispatch(ListActionBuilder.newListDataInvalidatedAction(listTypeIdentifier))
108+
}
109+
110+
/**
111+
* Emit RefreshList event - the ListStore component refetches the list of order ids from remote.
112+
*/
113+
private fun emitRefreshListEvent(localSiteId: LocalOrRemoteId.LocalId) {
114+
val listTypeIdentifier = WCOrderListDescriptor.calculateTypeIdentifier(
115+
localSiteId = localSiteId.value
116+
)
117+
dispatcher.dispatch(ListActionBuilder.newListRequiresRefreshAction(listTypeIdentifier))
118+
}
119+
}
120+
121+
Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,39 @@
11
package org.wordpress.android.fluxc.store
22

3+
import org.wordpress.android.fluxc.Dispatcher
4+
import org.wordpress.android.fluxc.generated.ListActionBuilder
5+
import org.wordpress.android.fluxc.model.LocalOrRemoteId
36
import org.wordpress.android.fluxc.model.OrderEntity
7+
import org.wordpress.android.fluxc.model.WCOrderListDescriptor
48
import org.wordpress.android.fluxc.persistence.TransactionExecutor
59
import org.wordpress.android.fluxc.persistence.dao.OrderMetaDataDao
6-
import org.wordpress.android.fluxc.persistence.dao.OrdersDao
10+
import org.wordpress.android.fluxc.persistence.dao.OrdersDaoDecorator
711
import org.wordpress.android.fluxc.persistence.entity.OrderMetaDataEntity
812
import javax.inject.Inject
913

1014
class InsertOrder @Inject internal constructor(
11-
private val ordersDao: OrdersDao,
15+
private val dispatcher: Dispatcher,
16+
private val ordersDaoDecorator: OrdersDaoDecorator,
1217
private val ordersMetaDataDao: OrderMetaDataDao,
1318
private val transactionExecutor: TransactionExecutor
1419
) {
15-
suspend operator fun invoke(vararg ordersPack: Pair<OrderEntity, List<OrderMetaDataEntity>>) {
20+
suspend operator fun invoke(
21+
localSiteId: LocalOrRemoteId.LocalId,
22+
vararg ordersPack: Pair<OrderEntity, List<OrderMetaDataEntity>>
23+
) {
1624
transactionExecutor.executeInTransaction {
1725
ordersPack.forEach { (order, metaData) ->
18-
ordersDao.insertOrUpdateOrder(order)
26+
ordersDaoDecorator.insertOrUpdateOrder(order, suppressListRefresh = true)
1927
ordersMetaDataDao.updateOrderMetaData(
2028
order.orderId,
2129
order.localSiteId,
2230
metaData
2331
)
2432
}
2533
}
34+
val listTypeIdentifier = WCOrderListDescriptor.calculateTypeIdentifier(
35+
localSiteId = localSiteId.value
36+
)
37+
dispatcher.dispatch(ListActionBuilder.newListDataInvalidatedAction(listTypeIdentifier))
2638
}
2739
}

0 commit comments

Comments
 (0)