-
Notifications
You must be signed in to change notification settings - Fork 136
Issue/woomob 1351 add logic to show or hide the booking tab dynamically #14640
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
Issue/woomob 1351 add logic to show or hide the booking tab dynamically #14640
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.
|
| import com.woocommerce.android.ciab.CIABSiteGateKeeper.Companion.CIAB_GARDEN_NAME | ||
| import org.wordpress.android.fluxc.model.SiteModel | ||
|
|
||
| fun SiteModel.isCIABSite() = isGardenSite && gardenName == CIAB_GARDEN_NAME |
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.
Unsure this is the best place for such extension. Open to suggestions
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.
We already have SiteModelExt for similar extensions, WDYT?
| selectedSite.observe() | ||
| .filter { it != null } | ||
| .collect { siteModel -> |
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.
We start observing bookable products only when we know for sure a site has been selected. Otherwise the app will crash. For example when loggin into the app for the first time, MainActivity is resumed before the app has finished selecting a site, leading to a crash.
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## trunk #14640 +/- ##
============================================
+ Coverage 38.49% 38.50% +0.01%
- Complexity 9767 9771 +4
============================================
Files 2064 2064
Lines 115429 115448 +19
Branches 15370 15372 +2
============================================
+ Hits 44430 44459 +29
+ Misses 66883 66872 -11
- Partials 4116 4117 +1 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
hichamboushaba
left a comment
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.
Nice work @JorgeMucientes, the logic is working as expected, but I think we can make some improvements to fix some issues that I shared below, please check the comments.
| import com.woocommerce.android.ciab.CIABSiteGateKeeper.Companion.CIAB_GARDEN_NAME | ||
| import org.wordpress.android.fluxc.model.SiteModel | ||
|
|
||
| fun SiteModel.isCIABSite() = isGardenSite && gardenName == CIAB_GARDEN_NAME |
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.
We already have SiteModelExt for similar extensions, WDYT?
| .onFailure { | ||
| // TODO log error or track errors? | ||
| selectedSite.observe() | ||
| .filter { it != 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.
I suggest using filterNotNull here, it will automatically cast the variable and allow you to get rid of the double-bang operator below.
| } | ||
| .map { productsCount -> | ||
| productsCount > 0 && | ||
| siteModel?.isCIABSite() == true && |
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.
siteModel is non-nullable here
| siteModel?.isCIABSite() == true && | |
| siteModel.isCIABSite() == true && |
| ), | ||
| excludeSampleProducts = true | ||
| ).onStart { | ||
| productListRepository.fetchProductList( |
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.
With this logic, we'll make an API request to fetch bookable products for all sites, WDYT about changing the order of things slightly to make sure we return an early false when the site is not CIAB, soemthing like the following:
operator fun invoke(siteModel: SiteModel): Flow<Boolean> = flow {
val isCIABSite = FeatureFlag.BOOKINGS_MVP.isEnabled() && siteModel.isCIABSite()
if (!isCIABSite) {
emit(false)
} else {
emitAll(
productListRepository.observeProductsCount(
filterOptions = mapOf(
ProductFilterOption.STATUS to ProductStatus.PUBLISH.value,
ProductFilterOption.TYPE to BOOKING_PRODUCT_TYPE
),
excludeSampleProducts = true
).map { count ->
count > 0
}.onStart {
productListRepository.fetchProductList(
productFilterOptions = mapOf(ProductFilterOption.TYPE to BOOKING_PRODUCT_TYPE)
).onFailure {
WooLog.w(WooLog.T.BOOKINGS, "Failed to fetch bookable products")
}
}.distinctUntilChanged()
)
}
}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.
Another point related to this, with the fetch happening in onStart, we don't emit any value until it's done, and this results in showing the bookings tab before hiding it when switching to any site that doesn't have the requirements:
Screen_recording_20250922_142723.mp4
This issue affects both your version and the above suggested version (for CIAB sites that don't have bookable products), we can avoid this by making the fetch async:
.onStart {
appCoroutineScope.launch {
productListRepository.fetchProductList(
productFilterOptions = mapOf(ProductFilterOption.TYPE to BOOKING_PRODUCT_TYPE)
).onFailure {
WooLog.w(WooLog.T.BOOKINGS, "Failed to fetch bookable products")
}
}
}| owner.lifecycle.removeObserver(this) | ||
| } | ||
|
|
||
| private fun checkBookingsTabVisibility() { |
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.
Not directly related to this PR, but just sharing an observation here, I think that given we use activity.lifecycleScope.launch in this function, it's safe to call it directly from init without making this class a LifecycleObserver, lifecycleScope already takes care of this, and won't start the Coroutine until the Activity is started.
We just need to ensure BookingsTabController subscribes to ObserveBookingsTabVisibility when the app starts, and unsubscribes when destroyed.activity.lifecycleScope takes care of this already so no need to make the class lifecycle aware.
| private val showBookingsTab: ShowBookingsTab | ||
| ) : DefaultLifecycleObserver { | ||
| private val observeBookingsTabVisibility: ObserveBookingsTabVisibility, | ||
| private val selectedSite: SelectedSite |
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.
❓ Is there a reason, we can't inject the selectedSite in ObserveBookingsTabVisibility and keep all the logic there?
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 didn't gave much though to it tbh. Any advantages I'm not seeing from having everything in ObserveBookingsTabVisibility?
I feel the current implementation is readable/clean enough, but I'm open to move the selected site to ObserveBookingsTabVisibility.
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.
Two things I see:
- Keeping the whole tab visibility logic in one place.
ObserveBookingsTabVisibilityTest- theBookingsTabControllerdoesn't have tests, and because of Android dependencies, it's a bit trickier to add them. Keeping everything inObserveBookingsTabVisibilitymakes it possible to test the whole flow, including the suspending issue mentioned with the previous code.
WooCommerce/src/main/kotlin/com/woocommerce/android/ui/bookings/tab/BookingsTabController.kt
Outdated
Show resolved
Hide resolved
|
Excellent feedback @hichamboushaba @AdamGrzybkowski thank you. Great eye for detail! I've addressed all your suggested feedback. Ready for another round :) |
hichamboushaba
left a comment
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.
Nice work @JorgeMucientes, thanks for taking care of this.
I won't merge this to give you a chance to check Adam's last remark about moving SelectedSite to ObserveBookingsTabVisibility.
AdamGrzybkowski
left a comment
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.
Tested and looks good 👍
The other comment is non-blocking so approving!
|
Thanks for all the feedback folks. I've moved the logic around observing selected site to |
Closes: WOOMOB-1351
Description
This is a follow-up PR to handle the visibility of the Booking tab dynamically based on the following criteria:
I've added a Flow to keep any changes around those 3 conditions reactive.
Testing information
To make tests quicker I recommed logging into the app with a WP.com account that has at least access to:
tools/garden/index.phpThe tests that have been performed
The above ☝🏼
Images/gif
Screen_recording_20250919_183429.mp4
RELEASE-NOTES.txtif necessary. Use the "[Internal]" label for non-user-facing changes.