Skip to content

feat: refresh fx rates on pull to refresh - #1281

Merged
jvsena42 merged 3 commits into
masterfrom
feat/622-refresh-rates-on-pull
Sep 18, 2026
Merged

jvsena42 merged 3 commits into
masterfrom
feat/622-refresh-rates-on-pull

Conversation

@jvsena42

@jvsena42 jvsena42 commented Sep 16, 2026

Copy link
Copy Markdown
Member

Fixes #622
Related to #1275

This PR makes pull to refresh on Home also refresh exchange rates.

Description

  • Adds an exchange rate refresh to the Home pull-to-refresh action so fiat values update on demand instead of waiting for the next 2-minute poll or app foreground.
  • Runs the rates refresh and the widgets refresh in separate coroutines so a slow widget refresh does not delay the rates fetch.
  • Replaces the Home view model's widgets-only refresh entry point with a single pull-to-refresh handler; it had no other callers.
  • Switches CurrencyRepo.refresh() to runSuspendCatching so a cancelled caller scope no longer records a CancellationException as a rates error; the isRefreshing reset moves into a finally so the re-entrancy guard is not left latched when cancellation propagates. Home is the first lifecycle-bound caller of triggerRefresh(), which is what makes the latent case reachable.
  • Adds a changelog fragment for the change.

Out of Scope

  • CurrencyRepo.kt: the isRefreshing guard is a non-atomic check-then-set, so a pull landing right as a poll starts can trigger a duplicate fetch. Pre-existing and harmless beyond the extra request.
  • Pull-to-refresh spinner: still bound to the wallet sync state only; it does not wait for the rates fetch.
  • Other pull-to-refresh screens (Node Info, Lightning Connections, Channel Detail, VSS debug) do not show fiat values and are unchanged.
  • iOS parity: bitkit-ios does not refresh rates on pull either; a matching iOS issue should carry this change and the journey below.

Design

N/A — no UI changes.

Preview

No layout change. Screenshots from the Pixel_9 emulator on this branch after two back-to-back pulls on Home: the refresh spinner shows and no "Rates currently unavailable" toast appears.

Double pull (journey run) Double pull (first check)
Home after two quick pulls, no rates error toast Home during double pull check, no error toast

QA Notes

Manual Tests

  • 1. Home → pull to refresh: app log gains a "Currency rates refreshed successfully" line within a few seconds of the pull, separate from the startup and 2-minute poll refreshes.
  • 2. Home → pull to refresh twice back to back: at most one rates refresh, no "Currency rates refresh failed" log line, no rates error toast.
  • 3. regression: Home → pull to refresh: wallet sync spinner shows and widgets (Price, News, Blocks, etc.) still refresh.

Verified on the Pixel_9 emulator (dev build): before this branch a Home pull refreshed the wallet and widgets but logged no rates refresh; on this branch a single pull logged the rates refresh alongside the widget updates, and double pulls produced one rates refresh with no error. No existing journey covered Home pull to refresh, so this adds one, committed on the branch as journeys/home/pull-to-refresh-rates.xml with its rows in the journeys/README.md suites and cross-platform tables (Android-only until the iOS parity work lands). Inline below for review:

journeys/home/pull-to-refresh-rates.xml (committed)
diff --git a/journeys/README.md b/journeys/README.md
index 403c5e230..90a4349a6 100644
--- a/journeys/README.md
+++ b/journeys/README.md
@@ -118,6 +118,7 @@ fixtures, push notifications) live in each suite's README.
 | [cjit-notifications](cjit-notifications) | 3 | CJIT channel-ready notifications; needs FCM push |
 | [deeplinks](deeplinks) | 2 | `bitkit://screen/…` and sheet routing behind the dev-mode gate; no README |
 | [hardware-wallet](hardware-wallet) | 17 | Trezor over USB; needs the Trezor emulator |
+| [home](home) | 1 | Pull to refresh on Home; checks the app log, no README |
 | [notification-permission](notification-permission) | 4 | Background-setup toggles |
 | [payment-requests](payment-requests) | 2 | Requires a linked fixture issuer; rejected shapes are unit fixtures |
 | [pubky-marketplace](pubky-marketplace) | 1 | Two-wallet Paykit marketplace payment; integration fixture required |
@@ -142,6 +143,7 @@ Known differences in the corpus, as of the iOS port (synonymdev/bitkit-ios#691):
 | `hardware-wallet/receive-onchain.xml`, `hardware-wallet/send-onchain.xml` | not ported |
 | `payment-requests/requested-resolution-failure.xml` | not ported |
 | `deeplinks/*` | not ported — iOS registers the `bitkit` scheme but has no screen or sheet router |
+| `home/pull-to-refresh-rates.xml` | not ported — iOS does not refresh exchange rates on pull to refresh |
 | — | `hardware-wallet/transfer-to-spending-over-max.xml` exists only on iOS |
 
 ### Running one on iOS
diff --git a/journeys/home/pull-to-refresh-rates.xml b/journeys/home/pull-to-refresh-rates.xml
new file mode 100644
index 000000000..1c0a2c8b6
--- /dev/null
+++ b/journeys/home/pull-to-refresh-rates.xml
@@ -0,0 +1,24 @@
+<journey name="pull to refresh rates">
+  <description>
+    Pulling to refresh on Home also refreshes exchange rates, not only the wallet, activity and
+    widgets. Precondition: onboarded dev wallet on Home (tag "HomeScrollView" visible), the Bitcoin
+    Price widget enabled, and network access to the rates backend. Rates also refresh on app start
+    and every two minutes of polling (`Env.fxRateRefreshInterval`), so pull at least 20s after the
+    last "Currency rates refreshed successfully" line and well before the next polling tick, or the
+    log check passes for the wrong reason. The success line and any error toast are not in
+    `android layout`; read the app log and take a screenshot. Log files rotate into `.part_NNN.log`
+    files mid-run, so list the logs again before each grep and read the newest one. Android only for
+    now: iOS does not refresh rates on pull.
+  </description>
+  <actions>
+    <action>Run `adb shell "run-as to.bitkit.dev ls -t files/logs/"` and note the newest log file</action>
+    <action>Run `adb shell "run-as to.bitkit.dev grep 'Currency rates refreshed' files/logs/&lt;newest&gt;"` and note the time of the last line</action>
+    <action>Verify the home screen (tag "HomeScrollView") is visible</action>
+    <action>Run `adb shell input swipe 540 700 540 1600 400` and note the UTC time</action>
+    <action>Verify the newest log (list the logs again) gains a "Currency rates refreshed successfully" line within 10s of the pull</action>
+    <action>Verify the same pull logs "Updated PRICE widget successfully" from WidgetsRepo</action>
+    <action>Run `adb shell input swipe 540 700 540 1600 400` twice back to back and take a screenshot</action>
+    <action>Verify no "Rates currently unavailable" toast is visible in the screenshot</action>
+    <action>Verify the newest log (list the logs again) gains at most one "Currency rates refreshed successfully" line for the two pulls and no "Currency rates refresh failed" line</action>
+  </actions>
+</journey>

Automated Checks

  • Unit tests added: HomeViewModelTest.kt verifies pull to refresh triggers both the rates refresh and the enabled widgets refresh, and that the widgets refresh starts while the rates refresh is still pending. CurrencyRepoTest.kt verifies a cancelled caller is not recorded as a rates error and does not latch the refresh guard.
  • Local verification: just compile, just test file CurrencyRepoTest, just test, just lint. Both new tests were checked against the pre-fix code and fail there. One full just test run hit a timing-sensitive failure in QuickPayRepoTest.kt (unrelated); it passed on targeted rerun and on a second full run.
  • CI: standard compile, unit test, and detekt checks run by the PR bot.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@jvsena42 jvsena42 self-assigned this Sep 16, 2026
@greptile-apps

greptile-apps Bot commented Sep 16, 2026

Copy link
Copy Markdown

RetriggerConfidence Score: 5/5

The PR appears safe to merge; the previously reported cancellation and concurrency-test concerns are fully addressed.

Summary

This PR extends Home pull-to-refresh to update exchange rates independently of widget refreshes and makes currency refresh cancellation lifecycle-safe.

  • Launches exchange-rate and widget refreshes in separate view-model coroutines.
  • Preserves coroutine cancellation without recording a false currency error.
  • Resets the currency refresh guard through finally.
  • Adds focused cancellation and concurrency tests plus a changelog fragment.

Diagram

sequenceDiagram
    actor User
    participant Home as HomeScreen
    participant VM as HomeViewModel
    participant Currency as CurrencyRepo
    participant Widgets as WidgetsRepo

    User->>Home: Pull to refresh
    Home->>VM: onPullToRefresh()
    par Independent rate refresh
        VM->>Currency: triggerRefresh()
        Currency->>Currency: Fetch and cache latest rates
    and Independent widget refresh
        VM->>Widgets: refreshEnabledWidgets()
    end
Loading

Reviews (2) · Last reviewed commit: "fix: preserve cancellation in currency r..."

Comment thread app/src/main/java/to/bitkit/ui/screens/wallets/HomeViewModel.kt
Comment thread app/src/test/java/to/bitkit/ui/screens/wallets/HomeViewModelTest.kt
@github-actions

github-actions Bot commented Sep 16, 2026

Copy link
Copy Markdown
Contributor

Regtest APK

Built from aa27b12 (run).

Download bitkit-dev-debug universal APK (expires in 30 days).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@jvsena42
jvsena42 marked this pull request as draft September 17, 2026 21:46
@jvsena42
jvsena42 marked this pull request as ready for review September 17, 2026 21:46

@jvsena42 jvsena42 left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Approved — no HIGH/MEDIUM findings at head 6c1340658. (Posted as a comment because GitHub does not allow approving your own PR.)

Checked: Traced HomeViewModel.onPullToRefresh() into CurrencyRepo.triggerRefresh() -> refresh() and the other callers (2-minute poll in repoScope, ContentView foreground refresh, setSelectedCurrency, dev settings). The if (isRefreshing) return early exit sits before the try, so a caller that skips because another refresh is in flight does not clear the flag owned by that other refresh; only the owner resets it in finally, including on cancellation. runSuspendCatching only changes behaviour for CancellationException: CurrencyService.fetchLatestRates() either rethrows it through the retry delay or throws it as lastError, and Ktor 3.3's HttpRequestTimeoutException is an IOException, so network timeouts are still recorded as rates errors and cannot escape into the polling collector and stop the poll loop. The pull-to-refresh indicator is still bound only to wallet sync state in HomeScreen, and the rates and widget refreshes are fire-and-forget in viewModelScope, so neither can leave the spinner stuck; a Home entry torn down mid-fetch now cancels cleanly without a false stale-rates toast.

@jvsena42 jvsena42 left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

✅ Manually reviewed

ovi-reviewer[bot]

This comment was marked as resolved.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

@ovi-reviewer ovi-reviewer Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Verdict: ✅ Approve


Reaudit: diff 2 files.

Findings:
N/A

Audit:
Already done in comment.

Coverage:
QA: journeys and manual tests await all reviewers to approve, author can run it now via comment: @ovi-reviewer test


Reviewed by claude-opus-5-xhigh via gh-pr-review-loop skill
Commands: @ovi-reviewer test · retest · audit (author or owner)

@ovitrif ovitrif left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

utAck

@jvsena42
jvsena42 merged commit 54372a0 into master Sep 18, 2026
19 checks passed
@jvsena42
jvsena42 deleted the feat/622-refresh-rates-on-pull branch September 18, 2026 13:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Re-fetch currency rates on pull-to-refresh

2 participants