Skip to content
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

Fix pagination load more #12545

Merged
merged 5 commits into from
Sep 6, 2024
Merged

Conversation

atorresveiga
Copy link
Contributor

@atorresveiga atorresveiga commented Sep 6, 2024

Closes: #12491

Description

This PR fixes the issue that prevented the load more behavior from triggering after reaching the end of the screen. The problem was that because of a timeout on the getListState function, the function returned a need-to-refresh state instead of the required to trigger the load more behavior can-load-more.

This PR fixes this issue with two main changes:

  1. It updates the FluxC version to use a new implementation that will not expire the final states can-load-more and fetched.
  2. Update the ShouldUpdateOrdersList to consider the list status. With this change, we aim to prevent inconsistent states in case the app is closed before reaching one of the final states.

Steps to reproduce

  1. Open the app
  2. Navigate to the orders screen
  3. Pull-to-refresh the screen
  4. Check that the last update timestamp is updated
  5. Cose the app and wait 2 min (more than 1 min will work too)
  6. Re-open the app
  7. Navigate to the orders screen
  8. Scroll to the bottom of the screen
  9. Check that the load more behavior is not triggered and only the first page is displayed

Testing information

  1. Open the app
  2. Navigate to the orders screen
  3. Pull-to-refresh the screen
  4. Check that the last update timestamp is updated
  5. Cose the app and wait 2 min (more than 1 min will work too)
  6. Re-open the app
  7. Navigate to the orders screen
  8. Scroll to the bottom of the screen
  9. Check that the pagination is working as expected

The tests that have been performed

  1. Test that pagination works as expected when data is loaded from a cache older than 1 min.
  2. Test that receiving an order notification updates the list
  3. Test that creating a new order displays the new order in the list
  4. Test that applying a status filter is working as expected
  5. Test that completing an order in the list with the "processing" filter removes the order from the list.

Images/gif

Screen_recording_20240905_210837.mp4
  • I have considered if this change warrants release notes and have added them to RELEASE-NOTES.txt if necessary. Use the "[Internal]" label for non-user-facing changes.

Reviewer (or Author, in the case of optional code reviews):

Please make sure these conditions are met before approving the PR, or request changes if the PR needs improvement:

  • The PR is small and has a clear, single focus, or a valid explanation is provided in the description. If needed, please request to split it into smaller PRs.
  • Ensure Adequate Unit Test Coverage: The changes are reasonably covered by unit tests or an explanation is provided in the PR description.
  • Manual Testing: The author listed all the tests they ran, including smoke tests when needed (e.g., for refactorings). The reviewer confirmed that the PR works as expected on big (tablet) and small (phone) in case of UI changes, and no regressions are added.

@atorresveiga atorresveiga added type: bug A confirmed bug. feature: order list Related to the order list. labels Sep 6, 2024
@atorresveiga atorresveiga added this to the 20.3 milestone Sep 6, 2024
@dangermattic
Copy link
Collaborator

1 Warning
⚠️ This PR is assigned to the milestone 20.3. This milestone is due in less than 2 days.
Please make sure to get it merged by then or assign it to a milestone with a later deadline.

Generated by 🚫 Danger

@wpmobilebot
Copy link
Collaborator

Found 1 violations:

The PR caused the following dependency changes:

expand

-+--- org.wordpress:fluxc:trunk-3d8b46e8151493d563fd519c4406a5b83bc95255
-|    +--- org.jetbrains.kotlin:kotlin-parcelize-runtime:1.9.25
-|    |    +--- org.jetbrains.kotlin:kotlin-stdlib:1.9.25 (*)
-|    |    \--- org.jetbrains.kotlin:kotlin-android-extensions-runtime:1.9.25
-|    |         \--- org.jetbrains.kotlin:kotlin-stdlib:1.9.25 (*)
-|    +--- androidx.exifinterface:exifinterface:1.0.0 -> 1.3.6
-|    |    \--- androidx.annotation:annotation:1.2.0 -> 1.8.0 (*)
-|    +--- androidx.security:security-crypto:1.0.0 -> 1.1.0-alpha03
-|    |    +--- androidx.annotation:annotation:1.1.0 -> 1.8.0 (*)
-|    |    +--- com.google.crypto.tink:tink-android:1.5.0
-|    |    \--- androidx.collection:collection:1.1.0 -> 1.4.0 (*)
-|    +--- com.squareup.okhttp3:okhttp-urlconnection:4.9.0
-|    |    +--- com.squareup.okhttp3:okhttp:4.9.0 -> 4.12.0 (*)
-|    |    \--- org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.4.10 -> 1.9.10 (*)
-|    +--- com.google.code.gson:gson:2.8.5 -> 2.10.1
-|    +--- org.apache.commons:commons-text:1.10.0 (*)
-|    +--- androidx.room:room-runtime:2.6.1 (*)
-|    +--- androidx.room:room-ktx:2.6.1
-|    |    +--- androidx.room:room-common:2.6.1 (*)
-|    |    +--- androidx.room:room-runtime:2.6.1 (*)
-|    |    +--- org.jetbrains.kotlin:kotlin-stdlib:1.8.22 -> 1.9.25 (*)
-|    |    +--- org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.1 -> 1.8.1 (*)
-|    |    +--- androidx.room:room-common:2.6.1 (c)
-|    |    \--- androidx.room:room-runtime:2.6.1 (c)
-|    +--- com.google.dagger:dagger:2.51.1
-|    |    \--- javax.inject:javax.inject:1
-|    +--- org.jetbrains.kotlinx:kotlinx-coroutines-core:1.8.1 (*)
-|    +--- org.jetbrains.kotlinx:kotlinx-coroutines-android:1.8.1 (*)
-|    +--- org.wordpress:wellsql:2.0.0
-|    |    +--- androidx.annotation:annotation:1.2.0 -> 1.8.0 (*)
-|    |    \--- org.wordpress.wellsql:wellsql-annotations:2.0.0
-|    +--- org.wordpress.fluxc:fluxc-annotations:trunk-3d8b46e8151493d563fd519c4406a5b83bc95255
-|    +--- org.greenrobot:eventbus-java:3.3.1
-|    +--- com.squareup.okhttp3:okhttp:4.9.0 -> 4.12.0 (*)
-|    +--- com.android.volley:volley:1.1.1 -> 1.2.0
-|    +--- androidx.paging:paging-runtime:2.1.2
-|    |    +--- androidx.paging:paging-common:2.1.2
-|    |    |    +--- androidx.annotation:annotation:1.0.0 -> 1.8.0 (*)
-|    |    |    \--- androidx.arch.core:core-common:2.0.0 -> 2.2.0 (*)
-|    |    +--- androidx.arch.core:core-runtime:2.0.0 -> 2.2.0 (*)
-|    |    +--- androidx.lifecycle:lifecycle-runtime:2.0.0 -> 2.7.0 (*)
-|    |    +--- androidx.lifecycle:lifecycle-livedata:2.0.0 -> 2.7.0 (*)
-|    |    \--- androidx.recyclerview:recyclerview:1.0.0 -> 1.3.2 (*)
-|    +--- com.goterl:lazysodium-android:5.0.2
-|    +--- net.java.dev.jna:jna:5.5.0
-|    \--- org.jetbrains.kotlin:kotlin-stdlib:1.9.25 (*)
++--- org.wordpress:fluxc:3090-3d6d1df2e25d7ea902359f75488bd95541c23df3
+|    +--- org.jetbrains.kotlin:kotlin-parcelize-runtime:1.9.25
+|    |    +--- org.jetbrains.kotlin:kotlin-stdlib:1.9.25 (*)
+|    |    \--- org.jetbrains.kotlin:kotlin-android-extensions-runtime:1.9.25
+|    |         \--- org.jetbrains.kotlin:kotlin-stdlib:1.9.25 (*)
+|    +--- androidx.exifinterface:exifinterface:1.0.0 -> 1.3.6
+|    |    \--- androidx.annotation:annotation:1.2.0 -> 1.8.0 (*)
+|    +--- androidx.security:security-crypto:1.0.0 -> 1.1.0-alpha03
+|    |    +--- androidx.annotation:annotation:1.1.0 -> 1.8.0 (*)
+|    |    +--- com.google.crypto.tink:tink-android:1.5.0
+|    |    \--- androidx.collection:collection:1.1.0 -> 1.4.0 (*)
+|    +--- com.squareup.okhttp3:okhttp-urlconnection:4.9.0
+|    |    +--- com.squareup.okhttp3:okhttp:4.9.0 -> 4.12.0 (*)
+|    |    \--- org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.4.10 -> 1.9.10 (*)
+|    +--- com.google.code.gson:gson:2.8.5 -> 2.10.1
+|    +--- org.apache.commons:commons-text:1.10.0 (*)
+|    +--- androidx.room:room-runtime:2.6.1 (*)
+|    +--- androidx.room:room-ktx:2.6.1
+|    |    +--- androidx.room:room-common:2.6.1 (*)
+|    |    +--- androidx.room:room-runtime:2.6.1 (*)
+|    |    +--- org.jetbrains.kotlin:kotlin-stdlib:1.8.22 -> 1.9.25 (*)
+|    |    +--- org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.1 -> 1.8.1 (*)
+|    |    +--- androidx.room:room-common:2.6.1 (c)
+|    |    \--- androidx.room:room-runtime:2.6.1 (c)
+|    +--- com.google.dagger:dagger:2.51.1
+|    |    \--- javax.inject:javax.inject:1
+|    +--- org.jetbrains.kotlinx:kotlinx-coroutines-core:1.8.1 (*)
+|    +--- org.jetbrains.kotlinx:kotlinx-coroutines-android:1.8.1 (*)
+|    +--- org.wordpress:wellsql:2.0.0
+|    |    +--- androidx.annotation:annotation:1.2.0 -> 1.8.0 (*)
+|    |    \--- org.wordpress.wellsql:wellsql-annotations:2.0.0
+|    +--- org.wordpress.fluxc:fluxc-annotations:3090-3d6d1df2e25d7ea902359f75488bd95541c23df3
+|    +--- org.greenrobot:eventbus-java:3.3.1
+|    +--- com.squareup.okhttp3:okhttp:4.9.0 -> 4.12.0 (*)
+|    +--- com.android.volley:volley:1.1.1 -> 1.2.0
+|    +--- androidx.paging:paging-runtime:2.1.2
+|    |    +--- androidx.paging:paging-common:2.1.2
+|    |    |    +--- androidx.annotation:annotation:1.0.0 -> 1.8.0 (*)
+|    |    |    \--- androidx.arch.core:core-common:2.0.0 -> 2.2.0 (*)
+|    |    +--- androidx.arch.core:core-runtime:2.0.0 -> 2.2.0 (*)
+|    |    +--- androidx.lifecycle:lifecycle-runtime:2.0.0 -> 2.7.0 (*)
+|    |    +--- androidx.lifecycle:lifecycle-livedata:2.0.0 -> 2.7.0 (*)
+|    |    \--- androidx.recyclerview:recyclerview:1.0.0 -> 1.3.2 (*)
+|    +--- com.goterl:lazysodium-android:5.0.2
+|    +--- net.java.dev.jna:jna:5.5.0
+|    \--- org.jetbrains.kotlin:kotlin-stdlib:1.9.25 (*)
-\--- org.wordpress.fluxc.plugins:woocommerce:trunk-3d8b46e8151493d563fd519c4406a5b83bc95255
-     +--- org.wordpress:fluxc:trunk-3d8b46e8151493d563fd519c4406a5b83bc95255 (*)
-     +--- com.google.code.gson:gson:2.8.5 -> 2.10.1
-     +--- com.google.dagger:dagger:2.51.1 (*)
-     +--- org.jetbrains.kotlinx:kotlinx-coroutines-core:1.8.1 (*)
-     +--- org.jetbrains.kotlinx:kotlinx-coroutines-android:1.8.1 (*)
-     +--- androidx.room:room-runtime:2.6.1 (*)
-     +--- org.wordpress:wellsql:2.0.0 (*)
-     +--- org.wordpress.fluxc:fluxc-annotations:trunk-3d8b46e8151493d563fd519c4406a5b83bc95255
-     +--- androidx.room:room-ktx:2.6.1 (*)
-     \--- org.jetbrains.kotlin:kotlin-stdlib:1.9.25 (*)
+\--- org.wordpress.fluxc.plugins:woocommerce:3090-3d6d1df2e25d7ea902359f75488bd95541c23df3
+     +--- org.wordpress:fluxc:3090-3d6d1df2e25d7ea902359f75488bd95541c23df3 (*)
+     +--- com.google.code.gson:gson:2.8.5 -> 2.10.1
+     +--- com.google.dagger:dagger:2.51.1 (*)
+     +--- org.jetbrains.kotlinx:kotlinx-coroutines-core:1.8.1 (*)
+     +--- org.jetbrains.kotlinx:kotlinx-coroutines-android:1.8.1 (*)
+     +--- androidx.room:room-runtime:2.6.1 (*)
+     +--- org.wordpress:wellsql:2.0.0 (*)
+     +--- org.wordpress.fluxc:fluxc-annotations:3090-3d6d1df2e25d7ea902359f75488bd95541c23df3
+     +--- androidx.room:room-ktx:2.6.1 (*)
+     \--- org.jetbrains.kotlin:kotlin-stdlib:1.9.25 (*)

Please review and act accordingly

@wpmobilebot
Copy link
Collaborator

📲 You can test the changes from this Pull Request in WooCommerce-Wear Android by scanning the QR code below to install the corresponding build.
App Name WooCommerce-Wear Android
Platform⌚️ Wear OS
FlavorJalapeno
Build TypeDebug
Commit0dfce14
Direct Downloadwoocommerce-wear-prototype-build-pr12545-0dfce14.apk

@wpmobilebot
Copy link
Collaborator

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

App Name WooCommerce Android
Platform📱 Mobile
FlavorJalapeno
Build TypeDebug
Commit0dfce14
Direct Downloadwoocommerce-prototype-build-pr12545-0dfce14.apk

@codecov-commenter
Copy link

codecov-commenter commented Sep 6, 2024

Codecov Report

All modified and coverable lines are covered by tests ✅

Project coverage is 40.60%. Comparing base (b8916af) to head (0dfce14).
Report is 6 commits behind head on trunk.

Additional details and impacted files
@@            Coverage Diff            @@
##              trunk   #12545   +/-   ##
=========================================
  Coverage     40.60%   40.60%           
- Complexity     5629     5632    +3     
=========================================
  Files          1223     1223           
  Lines         68595    68600    +5     
  Branches       9473     9475    +2     
=========================================
+ Hits          27851    27856    +5     
  Misses        38184    38184           
  Partials       2560     2560           

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

@atorresveiga atorresveiga merged commit 68993de into trunk Sep 6, 2024
15 of 16 checks passed
@atorresveiga atorresveiga deleted the issue/12491-fix-pagination-load-more branch September 6, 2024 04:47
class ShouldUpdateOrdersList @Inject constructor(private val lastUpdateDataStore: LastUpdateDataStore) {
suspend operator fun invoke(listId: Int): Boolean {
return lastUpdateDataStore.getLastUpdateKeyByOrdersListId(listId).let { key ->
class ShouldUpdateOrdersList @Inject constructor(
Copy link
Contributor

@malinajirka malinajirka Sep 6, 2024

Choose a reason for hiding this comment

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

@atorresveiga I'm wondering if we truly need this, it feels a bit off or at least unexpected to me. One of the main goals of the ListStore component is to manage lists for us automatically and avoid handling refreshes from the client. The ListState.NEEDS_REFRESH is an event emitted and consumed automatically within fluxC which should take care of updating the list for us and propagating that to the UI. We shouldn't need to check this state here and act on it. Could you please elaborate on why we need to handle this manually here?

P.S. I read the PR description, but I'm either misunderstanding it or it doesn't explain why a client side fix is needed.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Hi @malinajirka 👋 thank for sharing your concerns.

One of the main goals of the ListStore component is to manage lists for us automatically and avoid handling refreshes from the client.

While the ListStore component could provide many handy tools to implement pagination and the common logic to fetch fresh data, I feel that it should be up to the client apps to decide whether this data is stale. This is what happened with the Background Tasks project.

The Background Task project aimed to improve the app's perceived performance by relying more on cached data. As part of this project, we wanted to decide whether the cached data was outdated. For this, we defined a 30-minute threshold. If the cached data is not older than 30 minutes, we present that data; otherwise, we fetch the info from the API.

Could you please elaborate on why we need to handle this manually here?

Sure! As mentioned above, we wanted to check if the data in the DB was older than 30 minutes and decide if we needed to re-fetch everything or rely on that data cached. The decision of whether the data is stale or not is now owned by Woo. With this goal in mind, I implemented the ShouldUpdateOrdersList. This use case checks if the last update timestamp for the list description is still valid and returns a boolean that we use later to fetch the order's info or not.

Now, why do I need to check the ListState.NEEDS_REFRESH status?
In FluxC, we have implemented an expired logic for list states, you can check the function isListStateOutdated.

Here is the description for that function:

A helper function that returns whether it has been more than a certain time has passed since it's lastModified.
Since we keep the state in the DB, in the case of application being closed during a fetch, it'll carry over to the next session. To prevent such cases, we use a timeout approach. If it has been more than a certain time since the list is last updated, we should ignore the state.

So, there could be edge cases where the app is closed when the list is in an intermediary state (like fetching-frist-page). In those scenarios, I would want to refresh everything even if I have a valid timestamp. Does that make sense?
To sum up, I want the last update timestamp to be the source of truth when deciding whether to update the list or not, unless the list is in an inconsistent state. Let me know if this information helps! It's important to mention that as part of the FluxC PR, I prevented the final states can_load_more and fetched from expiring. This means we can still use them even if they are older than 1 minute, and because they are final states, I don't think there was a reason to discard them.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feature: order list Related to the order list. type: bug A confirmed bug.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Broken Order List Pagination
5 participants