feat: load real transaction history in demo app - #102
Conversation
…tate and native resource disposal
…from widget tests
|
Hi @j-kon, nice work here I noticed something. The Steps to reproduce: load wallet A → open Transactions → Load Transaction History → switch to wallet B → reopen Transactions. It still shows wallet A's transactions; tapping one resolves against B's repository and 404s as "Transaction not found." The sibling providers already handle this by keying off wallet identity, blockchain_providers.dart resets when activeWalletRecordProvider.id changes. Might be worth having the controller watch an activeWalletId (or ref.listen the record and reset) instead of a bool. Happy to be corrected if in-place switching isn't a supported flow. |
|
Thanks for the detailed. You were right that the transaction state was scoped only to wallet availability rather than the logical active wallet. I updated the transaction list and detail flow to use the active wallet record ID. Switching from wallet A to wallet B now clears the previous wallet’s transaction state, and stale asynchronous results are ignored if the active wallet changes before loading completes. I also added coverage for wallet switching so transaction list and detail data cannot leak between active wallets. |
|
Thanks @j-kon |
Johnosezele
left a comment
There was a problem hiding this comment.
Every time I leave the wallet/Home flow and navigate back to Transaction History, I land on "Transaction history not loaded yet" and have to tap Load Transaction History again, even right after a successful sync with txs already known to the wallet (Home shows the balance).
Likely related to transactionsControllerProvider being NotifierProvider.autoDispose.family keyed by wallet ID: leaving the page disposes the controller, so remount always returns TransactionsState.idle().
Please either:
- keep loaded history for the active wallet across navigations (drop autoDispose, or cache last success by wallet ID), or
- auto-load on page open when an active wallet is present,
and add widget coverage that pumps the list page, navigates away, returns and still shows (or auto-reloads) the previously loaded rows without an extra tap.
Johnosezele
left a comment
There was a problem hiding this comment.
After an incoming tx confirms, Home correctly updates (Balance + Trusted spendable reflect the synced wallet), but Transaction History can still show the earlier pending row until I leave, come back, and tap Reload Transaction History.
So for a while Home and History disagree: spendable says confirmed funds are available, history still says "Awaiting confirmation."
Root cause looks like split data paths:
- Home reads
wallet.balance()viabalanceSnapshotProvideron every successful sync - History only refreshes on explicit Load/Reload of
transactionsControllerProvider(and withautoDisposeit also drops to idle when leaving the route)
Please invalidate or auto-reload transaction history for the active wallet when sync completes (same moment balance is applied), so pending, confirmed cannot lag behind Home.
Widget coverage: load history while pending, then, apply post-sync wallet, and assert list shows confirmed without a manual reload.
|
Thanks @Johnosezele for the detailed review! I've updated PR #102 in commits
|
| .read(balanceSnapshotProvider.notifier) | ||
| .applyFromWallet(wallet, record.id); | ||
| ref | ||
| .read(transactionsControllerProvider(record.id).notifier) |
There was a problem hiding this comment.
Quick check on dropping this.
History refresh now relies on activeWalletProvider changing via replaceWallet...after broadcast we only applyFromWallet on the same wallet instance, so does the outgoing tx show in transaction history right away, or only after the next sync?
There was a problem hiding this comment.
Fixed in 0986214. Successful broadcasts now invalidate the wallet-scoped transaction controller, so an already-mounted history reloads immediately. Added widget coverage for this flow.
Johnosezele
left a comment
There was a problem hiding this comment.
I've left some questions and nits here
| FilledButton, | ||
| 'Load Transaction History', | ||
| ); | ||
| expect(tester.widget<FilledButton>(buttonFinder).onPressed, isNull); |
There was a problem hiding this comment.
nit: find.widgetWithText(FilledButton, …) breaks on newer Flutter, try to assert via the label + ButtonStyleButton ancestor...
There was a problem hiding this comment.
Fixed in 0986214. The test now finds the label and asserts through its ButtonStyleButton ancestor, avoiding the newer-Flutter FilledButton finder issue.
| ActiveWalletNotifier.new, | ||
| ); | ||
|
|
||
| final hasActiveWalletProvider = Provider<bool>((ref) { |
There was a problem hiding this comment.
hasActiveWalletProvider isn’t used anywhere, can we drop it?
There was a problem hiding this comment.
Fixed in 0986214. Removed the unused hasActiveWalletProvider; transaction views now use the wallet-bound availability provider.
| title: 'Transaction not found', | ||
| message: | ||
| 'No placeholder transaction was found for this txid.\n\n$txid', | ||
| 'No wallet transaction was found for this txid.\n\n$txid', |
There was a problem hiding this comment.
on detail, if there’s no active wallet the lookup comes back null, and we show the same “Transaction not found” card as for a missing txid... that makes it look like the tx is gone, when the issue is there’s no wallet loaded.
There was a problem hiding this comment.
Fixed in 0986214. The detail page now shows a dedicated no-active-wallet state before starting the transaction lookup, with widget coverage that keeps it distinct from a missing txid.
| mappedPosition = UnconfirmedTransactionPosition( | ||
| timestamp: position.timestamp, |
There was a problem hiding this comment.
timestamp is stored on UnconfirmedTransactionPosition but the mapper never reads it, keep for later UI or drop?
There was a problem hiding this comment.
Fixed in 0986214. Dropped the unused timestamp from UnconfirmedTransactionPosition and stopped carrying it through the mapper.
| final controllerProvider = transactionsControllerProvider(activeWalletId); | ||
| final state = ref.watch(controllerProvider); | ||
| final isLoading = state.status == TransactionsLoadState.loading; | ||
| final canLoad = activeWalletId != null && !isLoading; |
There was a problem hiding this comment.
canLoad only checks activeWalletId != null, not that an FFI wallet is loaded. A record without a wallet can still enable load and show empty success. Should we gate on activeWalletProvider or both?
There was a problem hiding this comment.
Fixed in 0986214. Loading now requires a matching logical wallet ID and live FFI wallet binding. Record-only and cleared-wallet states disable loading, clear stale rows, and have focused regression coverage.
|
Thanks for the detailed review, John. I’ve gone through the six open threads and I understand the concerns. The main issue is that the transaction history refresh path is still not fully consistent after broadcast, and there are also a few state and cleanup problems around wallet availability, empty states, and the tests. I haven’t fixed these yet. I’m going to address them one by one, add focused regression coverage, and avoid pushing another broad update until I’ve verified the behavior locally. I’ll reply to each thread with the exact change and test once it is done. Thanks for taking the time to review this carefully. |
|
@Johnosezele All six open review points are addressed in |
Summary
Context
Continues the transaction presentation scaffold from #62 by wiring the list/detail flow to active wallet transaction data instead of sample rows.
The production transaction repository uses the BDK Dart API to read wallet transactions. Transaction state remains auto-disposed and keyed by logical wallet ID, while an active wallet binding ensures each provider only reads from the matching FFI wallet.
Wallet replacement refreshes existing rows, wallet clearing removes stale rows, delayed results cannot cross wallet boundaries, and successful broadcasts invalidate the active wallet transaction controller.
Verification
dart format --output=none --set-exit-if-changed lib test example bdk_demo/lib bdk_demo/testdart analyze --fatal-infos --fatal-warnings lib test exampledart testflutter analyzeinsidebdk_demoflutter test --no-pub testinsidebdk_demoValidation passed: