Skip to content

Commit 5af14d8

Browse files
authored
Merge pull request #2178 from woocommerce/issue/927-refresh-app-on-foreground
Refresh Order List on app activation
2 parents 38a6175 + 42e2557 commit 5af14d8

File tree

4 files changed

+89
-0
lines changed

4 files changed

+89
-0
lines changed

RELEASE-NOTES.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
- Products: now the keyboard pop up automatically in Edit Description (M1), in Edit Short Description (M2), Edit Slug (M2), Edit Purchase Note (M2)
66
- The Processing orders list will now show upcoming (future) orders.
77
- Improved stats: fixed the incorrect time range on "This Week" tab when loading improved stats on a day when daily saving time changes.
8+
- The Orders list are now automatically refreshed when reopening the app.
89

910

1011
4.1

WooCommerce/Classes/ViewRelated/Orders/OrdersViewController.swift

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,17 @@ private extension OrdersViewController {
137137
/// Initialize ViewModel operations
138138
///
139139
func configureViewModel() {
140+
viewModel.onShouldResynchronizeAfterAppActivation = { [weak self] in
141+
guard let self = self,
142+
// Avoid synchronizing if the view is not visible. The refresh will be handled in
143+
// `viewWillAppear` instead.
144+
self.viewIfLoaded?.window != nil else {
145+
return
146+
}
147+
148+
self.syncingCoordinator.resynchronize()
149+
}
150+
140151
viewModel.activateAndForwardUpdates(to: tableView)
141152
}
142153

WooCommerce/Classes/ViewRelated/Orders/OrdersViewModel.swift

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,11 @@ final class OrdersViewModel {
2424
}
2525

2626
private let storageManager: StorageManagerType
27+
private let notificationCenter: NotificationCenter
28+
29+
/// The block called if self requests a resynchronization of the first page.
30+
///
31+
var onShouldResynchronizeAfterAppActivation: (() -> ())?
2732

2833
/// OrderStatus that must be matched by retrieved orders.
2934
///
@@ -37,6 +42,9 @@ final class OrdersViewModel {
3742
///
3843
private let includesFutureOrders: Bool
3944

45+
/// Used for tracking whether the app was _previously_ in the background.
46+
private var isAppActive: Bool = true
47+
4048
/// Should be bound to the UITableView to auto-update the list of Orders.
4149
///
4250
private lazy var resultsController: ResultsController<StorageOrder> = {
@@ -76,9 +84,11 @@ final class OrdersViewModel {
7684
}
7785

7886
init(storageManager: StorageManagerType = ServiceLocator.storageManager,
87+
notificationCenter: NotificationCenter = .default,
7988
statusFilter: OrderStatus?,
8089
includesFutureOrders: Bool = true) {
8190
self.storageManager = storageManager
91+
self.notificationCenter = notificationCenter
8292
self.statusFilter = statusFilter
8393
self.includesFutureOrders = includesFutureOrders
8494
}
@@ -91,6 +101,11 @@ final class OrdersViewModel {
91101
func activateAndForwardUpdates(to tableView: UITableView) {
92102
resultsController.startForwardingEvents(to: tableView)
93103
performFetch()
104+
105+
notificationCenter.addObserver(self, selector: #selector(handleAppDeactivation),
106+
name: UIApplication.willResignActiveNotification, object: nil)
107+
notificationCenter.addObserver(self, selector: #selector(handleAppActivation),
108+
name: UIApplication.didBecomeActiveNotification, object: nil)
94109
}
95110

96111
/// Execute the `resultsController` query, logging the error if there's any.
@@ -103,6 +118,21 @@ final class OrdersViewModel {
103118
}
104119
}
105120

121+
@objc private func handleAppDeactivation() {
122+
isAppActive = false
123+
}
124+
125+
/// Request a resynchornization if the app was previously in the background.
126+
///
127+
@objc private func handleAppActivation() {
128+
guard !isAppActive else {
129+
return
130+
}
131+
132+
isAppActive = true
133+
onShouldResynchronizeAfterAppActivation?()
134+
}
135+
106136
/// Returns what `OrderAction` should be used when synchronizing.
107137
///
108138
/// Pulling to refresh on filtered lists, like the Processing tab, will perform 2 network GET
@@ -159,9 +189,11 @@ final class OrdersViewModel {
159189
/// | Action | Current Tab | Delete All | GET ?status=processing | GET ?status=any |
160190
/// |------------------|-------------|------------|------------------------|-----------------|
161191
/// | Pull-to-refresh | Processing | y | y | y |
192+
/// | App activated | Processing | . | y | y |
162193
/// | `viewWillAppear` | Processing | . | y | y |
163194
/// | Load next page | Processing | . | y | . |
164195
/// | Pull-to-refresh | All Orders | y | . | y |
196+
/// | App activated | All Orders | . | . | y |
165197
/// | `viewWillAppear` | All Orders | . | . | y |
166198
/// | Load next page | All Orders | . | . | y |
167199
///

WooCommerce/WooCommerceTests/ViewRelated/Orders/OrdersViewModelTests.swift

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ final class OrdersViewModelTests: XCTestCase {
3535
super.tearDown()
3636
}
3737

38+
// MARK: - Synchronization Spec
39+
3840
// Test that when pulling to refresh on a filtered list (e.g. Processing tab), the action
3941
// returned will be for:
4042
//
@@ -188,6 +190,8 @@ final class OrdersViewModelTests: XCTestCase {
188190
XCTAssertEqual(pageSize, self.pageSize)
189191
}
190192

193+
// MARK: - Future Orders
194+
191195
func testGivenAFilterItLoadsTheOrdersMatchingThatFilterFromTheDB() {
192196
// Arrange
193197
let viewModel = OrdersViewModel(storageManager: storageManager,
@@ -321,6 +325,47 @@ final class OrdersViewModelTests: XCTestCase {
321325
XCTAssertEqual(Age(rawValue: upcomingSection.name), .upcoming)
322326
XCTAssertEqual(upcomingSection.numberOfObjects, expectedOrders.future.count)
323327
}
328+
329+
// MARK: - App Activation
330+
331+
func testItRequestsAResynchronizationWhenTheAppIsActivated() {
332+
// Arrange
333+
let notificationCenter = NotificationCenter()
334+
let viewModel = OrdersViewModel(notificationCenter: notificationCenter, statusFilter: nil)
335+
336+
var resynchronizeRequested = false
337+
viewModel.onShouldResynchronizeAfterAppActivation = {
338+
resynchronizeRequested = true
339+
}
340+
341+
viewModel.activateAndForwardUpdates(to: UITableView())
342+
343+
// Act
344+
notificationCenter.post(name: UIApplication.willResignActiveNotification, object: nil)
345+
notificationCenter.post(name: UIApplication.didBecomeActiveNotification, object: nil)
346+
347+
// Assert
348+
XCTAssertTrue(resynchronizeRequested)
349+
}
350+
351+
func testGivenNoPreviousDeactivationItDoesNotRequestAResynchronizationWhenTheAppIsActivated() {
352+
// Arrange
353+
let notificationCenter = NotificationCenter()
354+
let viewModel = OrdersViewModel(notificationCenter: notificationCenter, statusFilter: nil)
355+
356+
var resynchronizeRequested = false
357+
viewModel.onShouldResynchronizeAfterAppActivation = {
358+
resynchronizeRequested = true
359+
}
360+
361+
viewModel.activateAndForwardUpdates(to: UITableView())
362+
363+
// Act
364+
notificationCenter.post(name: UIApplication.didBecomeActiveNotification, object: nil)
365+
366+
// Assert
367+
XCTAssertFalse(resynchronizeRequested)
368+
}
324369
}
325370

326371
// MARK: - Helpers

0 commit comments

Comments
 (0)