Skip to content

Conversation

joshheald
Copy link
Contributor

Merge after: #16231

Description

This PR starts using the observable GRDB data source added in #16231 to show items from GRDB in the item list.

Note that there's nothing to enforce that GRDB actually has items in it at the moment... so you can definitely break this, but those guards are covered by other tickets and this is feature flagged.

Steps to reproduce

Check we're not breaking production...

  • Set the pointOfSaleLocalCatalogi1 feature flag to false
  • Open the POS
  • Observe that browsing and scrolling works as expected, and use WormHoly or a proxy tool to verify that products are loaded from the network.

Test the GRDB item controller

  • Reset the feature flag
  • Launch the app
  • Wait for console log messages to confirm the catalog sync status for your store
  • Open POS
  • Observe that products are shown, and that you can open variation lists
  • Scroll down, observe that subsequent pages are loaded
  • Turn on airplane mode, observe that you can still fully browse the catalog.

Example sync log messages:

📋 POSCatalogSyncCoordinator: Site 216335538 has catalog size 91, with 45 products and 46 variations
📋 POSCatalogSyncCoordinator: Last sync for site 216335538 was 16344s ago (max: 86400s), sync not needed

Optional: test with a larger site

  • Change the value for POSCatalogSyncCoordinator.Constants.defaultSizeLimitForPOSCatalog to 100000
  • Run the app and pick a store with a large catalog
  • Wait for the sync to complete (it will take some time and gives no progress info before completion)
  • Open POS
  • Observe that you can interact with the full catalog

Note that with larger catalogs, you start to hit scrolling performance issues. These are outside the scope of current work, but we may want to address them before too long. I don't think it's down to the mapping happening on the main thread, and it may even be that we're hitting some SwiftUI limitations.

Testing information

Note that Search and Barcode Scanning are still using the network-based controller, so have delays.

Screenshots

GRDB.items.mp4

  • I have considered if this change warrants user-facing release notes and have added them to RELEASE-NOTES.txt if necessary.

Adds a new `.initial` case to `ItemListState` to represent "not yet loaded" state, distinct from "actively loading".

Benefits:
- More explicit state machine: initial → loading → loaded/empty/error
- Foundation for delayed loading indicators (300ms threshold)
- Better UX for instant GRDB loads (no flashing skeleton)

Updated existing `PointOfSaleItemsController` to:
- Start with `.initial` instead of `.loading([])`
- Transition to `.loading([])` on first load
- Preserve existing behavior for refreshes
Creates a new controller that wraps `POSObservableDataSourceProtocol` for GRDB-based item loading.

Key features:
- Conforms to `PointOfSaleItemsControllerProtocol` (same interface as existing controller)
- Computed `itemsViewState` from observable data source properties
- Automatic UI updates via observation (no manual state management)
- Supports products and variations with `.initial` state
- Tracks current parent for variation state mapping

Implementation notes:
- No pagination tracking needed (data source handles it)
- No search support (out of scope for this PR)
Comprehensive test coverage for the new observable controller:

- Initial state (loading container, initial root)
- Product loading (loaded, empty, error states)
- Variation loading (loaded, empty states)
- Pagination (hasMoreItems flags)
- Independence of products and variations
- Parent switching behavior
- Error handling
- Loading state preservation

Fix tests to use proper POSItem construction

Use helper methods to create POSSimpleProduct and POSVariation structs
instead of non-existent .fake() method, matching the pattern used in
MockPOSItemProvider.

Changes:
- Added makeSimpleProduct() helper
- Added makeVariation() helper
- Updated all 12 tests to use proper construction
When grdbManager, catalogSyncCoordinator, and the pointOfSaleLocalCatalogi1
feature flag are all present, use the new PointOfSaleObservableItemsController
backed by GRDBObservableDataSource instead of the standard
PointOfSaleItemsController.

This enables reactive database-driven item lists for stores with local
catalog sync enabled.
@joshheald joshheald added this to the 23.5 milestone Oct 10, 2025
@joshheald joshheald requested a review from staskus October 10, 2025 14:01
@joshheald joshheald added type: task An internally driven task. status: feature-flagged Behind a feature flag. Milestone is not strongly held. feature: POS labels Oct 10, 2025
@joshheald
Copy link
Contributor Author

@staskus Sorry it took longer than I thought it would, but here's the second PR as mentioned this morning.

I'm AFK next week – happy for you to merge them then if you prefer not to review this afternoon. As long as there's nothing major needed as changes!

import enum Yosemite.POSItem

enum ItemListState {
case initial
Copy link
Contributor Author

@joshheald joshheald Oct 10, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added this state for more accurate modelling of what's happening.

My intention is to delay showing the loading indicators when we open variations with GRDB – it causes a slight flicker at the moment. This is some groundwork for that.

https://linear.app/a8c/issue/WOOMOB-1494/woo-poslocal-catalog-add-a-delay-before-showing-loading-indicators

@wpmobilebot
Copy link
Collaborator

wpmobilebot commented Oct 10, 2025

App Icon📲 You can test the changes from this Pull Request in WooCommerce iOS Prototype by scanning the QR code below to install the corresponding build.

App NameWooCommerce iOS Prototype
Build Numberpr16235-eba2394
Version23.4
Bundle IDcom.automattic.alpha.woocommerce
Commiteba2394
Installation URL37ijp1hl1mnuo
Automatticians: You can use our internal self-serve MC tool to give yourself access to those builds if needed.

@joshheald
Copy link
Contributor Author

joshheald commented Oct 10, 2025

@staskus I also tested that it observes changes: this branch adds an incremental sync button to the ... menu. I used that by editing products on the web and then syncing.

Trashing a product didn't remove it, which is probably down to how we request them or filter the products from the db... but I'll fix it in a separate PR. https://linear.app/a8c/issue/WOOMOB-1493/woo-poslocal-catalog-ensure-that-trashed-products-are-updated-to-not

Other changes and additions worked as expected.

@staskus staskus self-assigned this Oct 14, 2025
func loadItems(base: ItemListBaseItem) async {
switch base {
case .root:
hasLoadedProducts = true
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we delay it until the items are loaded? Same with other similar flags.

func refreshItems(base: ItemListBaseItem) async {
switch base {
case .root:
dataSource.refresh()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So, this is not implemented. Is the idea to trigger an incremental sync when refresh is called?

}

// Error state
if let error = dataSource.error, items.isEmpty {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What kind of errors can we expect here? Since this comes from database, only something super-unexpected I suppose?

Copy link
Contributor

@staskus staskus left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you, @joshheald, for work! It took some time to review and understand. I'm not yet that familiar with GRDB, plus there's complexity in clean state management and observability. We don't want the views to stuck in some "no man's land" state.

I think it's mostly good. The comments are in both PRs. I tested with smaller and larger stores. I think there are a couple of things that need to be addressed. I don't see much value in pushing this to trunk without addressing and understanding them properly. I will see if I find time to come back to this after finishing the synchronization work. Otherwise, we can discuss next week and fix things or move to other tasks and merge this work.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feature: POS status: feature-flagged Behind a feature flag. Milestone is not strongly held. type: task An internally driven task.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants