-
Notifications
You must be signed in to change notification settings - Fork 133
[Woo POS][Local Catalog] Catalog full sync required #14678
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: trunk
Are you sure you want to change the base?
[Woo POS][Local Catalog] Catalog full sync required #14678
Conversation
📲 You can test the changes from this Pull Request in WooCommerce-Wear Android by scanning the QR code below to install the corresponding build.
|
📲 You can test the changes from this Pull Request in WooCommerce Android by scanning the QR code below to install the corresponding build.
|
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## trunk #14678 +/- ##
============================================
- Coverage 38.27% 38.26% -0.02%
- Complexity 10010 10018 +8
============================================
Files 2118 2120 +2
Lines 118488 118668 +180
Branches 15824 15849 +25
============================================
+ Hits 45357 45410 +53
- Misses 68917 69041 +124
- Partials 4214 4217 +3 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
…n-pos-splash' into woomob-1412-woo-poslocal-catalog-full-sync-overdue-handling
Generated by 🚫 Danger |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for working on this @samiuelson!
I'm not entirely sure what is in scope of this PR and what is out of scope. I'll list issues I encountered, feel free to ignore those that are out of scope.
- When we are doing an initial full sync, the blocking screen should be full screen (part of splash screen):

- I noticed that currently during the full sync, the blocking layer is semi-transparent, so I can see products already loaded below the loading state:

- I can see loading skeletons during an incremental sync - the incremental sync shouldn't be visible to the user as it goes against the speed we are trying to achieve.
Screen.Recording.2025-10-08.at.8.16.52.mov
-
Not sure if it's an issue, but I can see the loading skeletons for 0.5seconds even when I turn off the feature flag even though we load the items during the splashscreen.
-
The POS is not accessible when there is no internet connection even though I have data in the database + I can see the data behind the error:

Let me know what you think.
WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeViewModel.kt
Show resolved
Hide resolved
...rc/main/kotlin/com/woocommerce/android/ui/woopos/localcatalog/WooPosFullSyncStatusChecker.kt
Outdated
Show resolved
Hide resolved
...rc/main/kotlin/com/woocommerce/android/ui/woopos/localcatalog/WooPosFullSyncStatusChecker.kt
Outdated
Show resolved
Hide resolved
} | ||
|
||
private fun isFullSyncOverdue(lastSyncTimestamp: Long): Boolean { | ||
val currentTime = System.currentTimeMillis() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🔍 This code doesn't seem testable, we might want to inject time provider.
when (workInfo?.state) { | ||
WorkInfo.State.SUCCEEDED -> { | ||
val completedTimestamp = syncTimestampManager.getFullSyncLastCompletedTimestamp() | ||
if (completedTimestamp != null) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
❓ How can this scenario happen?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@malinajirka 👋 Most common case I can think of - completedTimestamp
can be null here, for example, in case the site was switched during full sync. The completedTimestamp
would be null for the newly selected site, even though the worker would finish full sync with success for the previous site. WooPosSyncTimestampRepository
handles values specific to the currently selected site.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This makes me think - maybe we should schedule sync workers with names unique to the currently selected site? This way we could make sure at POS splash screen, that the active worker is syncing data of the currently selected site. Wdyt, @malinajirka ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@samiuelson I'm up for it, but it might not be trivial I think - the worker should likely be scheduled in app startup and the user might not be logged in yet. I'd be a bit worried we might end up in a situation where we have a bunch of workers waking up the device too often.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Makes sense. In that case, relying on getFullSyncLastCompletedTimestamp
to verify if the completed sync was related to the currently selected store should be good enough, IMO.
private suspend fun FlowCollector<WooPosFullSyncState>.monitorOneTimeWorkerProgress() { | ||
emit(WooPosFullSyncState.InProgress) | ||
|
||
val finalWorkInfo = syncScheduler.observeOneTimeWorkInfo() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
❓ It seems the worker as well as this code runs in a coroutine (not main thread) => are we guaranteed that between
val isOneTimeRunning = syncScheduler.observeOneTimeWorkStatus().first()
and
val finalWorkInfo = syncScheduler.observeOneTimeWorkInfo().filter { workInfo -> workInfo?.state?.isFinished == true } .first()
the worker doesn't finish? I'm not sure, but I have a gut feeling this code might lead to a deadlock.
I think we could fix it by completing this observer also when workInfo
is null (aka if no work is in progress anymore). Wdyt?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good catch. I tried improving it in 6190d97
WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeViewModel.kt
Outdated
Show resolved
Hide resolved
...rc/main/kotlin/com/woocommerce/android/ui/woopos/localcatalog/WooPosFullSyncStatusChecker.kt
Show resolved
Hide resolved
…overdue-handling Resolved conflicts: - WooPosLocalCatalogSyncWorker.kt: Combined timestamp storage with incremental sync - WooPosLocalCatalogSyncScheduler.kt: Merged all required coroutine imports Fixed compilation errors: - WooPosItemsScreen.kt: Updated to use WooPosErrorScreenButtonState instead of private Button component 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]>
That was fixed 👍
Let me improve 3. and 4. in a separate PR, as this one grew too big—sorry for that, BTW! I created task for this: WOOMOB-1525
Good catch. This was fixed 👍 Thank you for the review, @malinajirka! I addressed your comments. Also, sorry for the long PR I failed to keep it within a limit. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull Request Overview
Copilot reviewed 22 out of 22 changed files in this pull request and generated 5 comments.
Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.
...rc/main/kotlin/com/woocommerce/android/ui/woopos/localcatalog/WooPosFullSyncStatusChecker.kt
Show resolved
Hide resolved
...kotlin/com/woocommerce/android/ui/woopos/localcatalog/WooPosPerformInstantCatalogFullSync.kt
Outdated
Show resolved
Hide resolved
...kotlin/com/woocommerce/android/ui/woopos/localcatalog/WooPosPerformInstantCatalogFullSync.kt
Outdated
Show resolved
Hide resolved
WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/splash/WooPosSplashScreen.kt
Show resolved
Hide resolved
WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/WooPosItemsScreen.kt
Outdated
Show resolved
Hide resolved
Co-authored-by: Copilot <[email protected]>
Version |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks @samiuelson for iterating on the PR! I've reviewed the code and it all looks good to me. However, I've encountered a couple issues when testing the PR. I'd consider merging it as is and fixing the bugs in a separate PR, wdyt?
Infinite splash screen
- Downgrade your site to WooCommerce 10.2 or older
- Clean apps data
- Login
- Enter POS
- Notice catalog is syncing -> notice
/variations
requests fail since the endpoint isn't available in v10.2 => the logs mentionWorker result RETRY
which isn't properly handled (it goes again intoENQUEUED
state on Retry).

POS Opened with empty catalog
- Clean apps data
- Login
- Turn off wifi/data
- Open POS
- Notice expected error
- Tap on retry
- Turn on wifi/data
- Tap on retry
- Notice you are allowed to enter POS before the initial sync completes.
My.Movie.mp4
startTime: Long | ||
) = FlowCollector<WooPosLocalCatalogInitialFullSyncState> { syncState -> | ||
when (syncState) { | ||
is WooPosLocalCatalogInitialFullSyncState.Ready -> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🔍 np: I'd consider calling this NotRequired instead of Ready. At first I thought Ready
means that the sync is about to start.
abstract suspend fun getProduct(localSiteId: LocalId, remoteId: RemoteId): WooPosProductEntity? | ||
|
||
@Query("SELECT COUNT(*) FROM PosProductEntity WHERE localSiteId = :localSiteId") | ||
abstract suspend fun getProductCount(localSiteId: LocalId): Int |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
📓 I think this is fine for the current use case, but we need to be careful, since getProductCount
returns count of all products including those which are not supported by the POS (hidden in the POS).
wooPosLogWrapper.e("Full sync check failed: No site selected") | ||
return WooPosFullSyncRequirement.Error("No site selected") | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💡 I'm wondering if we need to check isPeriodicSyncEnabledForSite
here as well. Otherwise, this class might require sync while the worker always finishes with failure
on line WooPosLocalCatalogSyncWorker::54. Wdyt?
P.S. Feel free to create a separate ticket.
WOOMOB-1412
Description
The goal of PR is to improve the reliability of the full sync of POS local catalog.
The full sync is scheduled to be performed every 24h, at night. However, in case the device was turned off at night, the sync may not succeed. To handle such case, this PR adds:
trunk
.Steps to reproduce
The full sync should be performed in a blocking way before allowing access to the POS for the first time (or in case product data is missing)
Testing information
The tests that have been performed
Above.
Images/gif
Screen_recording_20251007_181723.mp4
RELEASE-NOTES.txt
if necessary. Use the "[Internal]" label for non-user-facing changes.