From bebdb8867b476184e17e2013866ec7d22b55f94b Mon Sep 17 00:00:00 2001 From: joshuakrueger-dfx Date: Wed, 3 Jun 2026 23:19:01 +0200 Subject: [PATCH] fix(dashboard): handle balance-stream errors instead of freezing the balance MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit BalanceCubit subscribed with watchBalance(...).listen(emit) and no onError, so a stream error escaped as an unhandled async error and the balance silently froze with no recovery. Add an onError handler that logs the failure; with cancelOnError defaulting to false the subscription stays alive, so later balances still update the UI. Regression: test/screens/dashboard/balance_cubit_test.dart ("a stream error is handled ... and later balances still update") Issue #657 — Part 3, finding #14. --- lib/screens/dashboard/bloc/balance_cubit.dart | 17 +++++++++++- .../screens/dashboard/balance_cubit_test.dart | 26 +++++++++++++++++++ 2 files changed, 42 insertions(+), 1 deletion(-) diff --git a/lib/screens/dashboard/bloc/balance_cubit.dart b/lib/screens/dashboard/bloc/balance_cubit.dart index c89d39de1..9e9689c80 100644 --- a/lib/screens/dashboard/bloc/balance_cubit.dart +++ b/lib/screens/dashboard/bloc/balance_cubit.dart @@ -1,4 +1,5 @@ import 'dart:async'; +import 'dart:developer' as developer; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:realunit_wallet/models/asset.dart'; @@ -19,7 +20,21 @@ class BalanceCubit extends Cubit { asset: asset, ), ) { - _subscription = _repository.watchBalance(state).listen(emit); + // Register an onError handler so a balance-stream error is logged instead + // of escaping as an unhandled async error that silently freezes the + // balance (issue #657 P3 #14). cancelOnError defaults to false, so the + // subscription stays alive and later balances still update the UI. + _subscription = _repository.watchBalance(state).listen( + emit, + onError: (Object error, StackTrace stackTrace) { + developer.log( + 'balance stream error', + name: '$BalanceCubit', + error: error, + stackTrace: stackTrace, + ); + }, + ); } final BalanceRepository _repository; diff --git a/test/screens/dashboard/balance_cubit_test.dart b/test/screens/dashboard/balance_cubit_test.dart index 5b7aaf20b..c9b92d40d 100644 --- a/test/screens/dashboard/balance_cubit_test.dart +++ b/test/screens/dashboard/balance_cubit_test.dart @@ -71,6 +71,32 @@ void main() { expect(cubit.state.balance, BigInt.from(12345)); }); + test( + 'a stream error is handled (not unhandled) and later balances still ' + 'update (issue #657 P3 #14 regression)', () async { + final cubit = build(); + + // Without an onError handler this error escaped as an unhandled async + // error (failing the test) and froze the balance. It must now be + // swallowed/logged, and — cancelOnError being false — a balance pushed + // afterwards must still reach the state. + controller.addError(Exception('balance backend blip')); + await Future.delayed(Duration.zero); + + final updated = Balance( + chainId: realUnitAsset.chainId, + contractAddress: realUnitAsset.address, + walletAddress: _address, + balance: BigInt.from(999), + asset: realUnitAsset, + ); + final ready = cubit.stream.firstWhere((b) => b.balance == BigInt.from(999)); + controller.add(updated); + await ready.timeout(const Duration(seconds: 1)); + + expect(cubit.state.balance, BigInt.from(999)); + }); + test('close() cancels the underlying stream subscription', () async { final cubit = build();