From c89d82506bc050de1d3c39fab71571cf54865be0 Mon Sep 17 00:00:00 2001 From: naezith Date: Tue, 27 Jun 2023 12:33:07 +0300 Subject: [PATCH 01/24] create WalletAddress --- .../models/wallet_address.dart | 29 +++++++++++++++++++ .../models/wallet_address.g.dart | 26 +++++++++++++++++ pubspec.lock | 14 +++++++++ pubspec.yaml | 2 ++ 4 files changed, 71 insertions(+) create mode 100644 lib/packages/account_addresses/models/wallet_address.dart create mode 100644 lib/packages/account_addresses/models/wallet_address.g.dart diff --git a/lib/packages/account_addresses/models/wallet_address.dart b/lib/packages/account_addresses/models/wallet_address.dart new file mode 100644 index 000000000..8e8320bbd --- /dev/null +++ b/lib/packages/account_addresses/models/wallet_address.dart @@ -0,0 +1,29 @@ +import 'package:hive/hive.dart'; + +part 'wallet_address.g.dart'; // Name of the TypeAdapter generated by Hive + +@HiveType(typeId: 0) +class WalletAddress { + @HiveField(0) + String walletId; + + @HiveField(1) + String address; + + @HiveField(2) + String ticker; + + @HiveField(3) + double availableBalance; + + @HiveField(4) + String accountId; + + WalletAddress({ + this.walletId, + this.address, + this.ticker, + this.availableBalance, + this.accountId, + }); +} diff --git a/lib/packages/account_addresses/models/wallet_address.g.dart b/lib/packages/account_addresses/models/wallet_address.g.dart new file mode 100644 index 000000000..80788f2c9 --- /dev/null +++ b/lib/packages/account_addresses/models/wallet_address.g.dart @@ -0,0 +1,26 @@ +part of 'wallet_address.dart'; + +class WalletAddressAdapter extends TypeAdapter { + @override + final typeId = 0; + + @override + WalletAddress read(BinaryReader reader) { + return WalletAddress( + walletId: reader.read(), + address: reader.read(), + ticker: reader.read(), + availableBalance: reader.read(), + accountId: reader.read(), + ); + } + + @override + void write(BinaryWriter writer, WalletAddress obj) { + writer.write(obj.walletId); + writer.write(obj.address); + writer.write(obj.ticker); + writer.write(obj.availableBalance); + writer.write(obj.accountId); + } +} diff --git a/pubspec.lock b/pubspec.lock index e5dea6c62..7cee58b04 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -434,6 +434,20 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "0.2.0" + hive: + dependency: "direct main" + description: + name: hive + url: "https://pub.dartlang.org" + source: hosted + version: "2.2.3" + hive_flutter: + dependency: "direct main" + description: + name: hive_flutter + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.0" http: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index 3b481cbb0..4f9b1f2fb 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -167,6 +167,8 @@ dependencies: # "Flutter Favorite" plugin (https://docs.flutter.dev/packages-and-plugins/favorites) flutter_local_notifications: ^12.0.4 # TODO: Secure code review of this plugin. + hive: ^2.2.3 + hive_flutter: ^1.1.0 From b7d13c197dcb5031bd0978dc5d212406578ce9f3 Mon Sep 17 00:00:00 2001 From: naezith Date: Tue, 27 Jun 2023 12:33:18 +0300 Subject: [PATCH 02/24] create AccountAddressesApiInterface --- .../api/account_addresses_api_interface.dart | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 lib/packages/account_addresses/api/account_addresses_api_interface.dart diff --git a/lib/packages/account_addresses/api/account_addresses_api_interface.dart b/lib/packages/account_addresses/api/account_addresses_api_interface.dart new file mode 100644 index 000000000..26851b997 --- /dev/null +++ b/lib/packages/account_addresses/api/account_addresses_api_interface.dart @@ -0,0 +1,12 @@ +import 'package:komodo_dex/packages/account_addresses/models/wallet_address.dart'; + +abstract class AccountAddressesApiInterface { + Future create(WalletAddress walletAddress); + Future update(String walletId, String address, + {WalletAddress updateFields}); + Future deleteOne(String walletId, String address); + Future deleteAll(String walletId); + Future readOne(String walletId, String address); + Future> readAll(String walletId); + Stream watchAll(String walletId); +} From 4ee8952fcfcb52e6d060378cde815295e8b58f18 Mon Sep 17 00:00:00 2001 From: naezith Date: Tue, 27 Jun 2023 12:37:26 +0300 Subject: [PATCH 03/24] create AccountAddressesApiHive --- .../api/account_addresses_api_hive.dart | 74 +++++++++++++++++++ 1 file changed, 74 insertions(+) create mode 100644 lib/packages/account_addresses/api/account_addresses_api_hive.dart diff --git a/lib/packages/account_addresses/api/account_addresses_api_hive.dart b/lib/packages/account_addresses/api/account_addresses_api_hive.dart new file mode 100644 index 000000000..27e5ab46b --- /dev/null +++ b/lib/packages/account_addresses/api/account_addresses_api_hive.dart @@ -0,0 +1,74 @@ +import 'package:hive/hive.dart'; +import 'package:hive_flutter/hive_flutter.dart'; +import 'package:komodo_dex/packages/account_addresses/api/account_addresses_api_interface.dart'; +import 'package:komodo_dex/packages/account_addresses/models/wallet_address.dart'; + +class AccountAddressesApiHive implements AccountAddressesApiInterface { + Box _box; + + AccountAddressesApiHive() { + Hive.registerAdapter(WalletAddressAdapter()); + Hive.openBox('account_addresses').then((box) => _box = box); + } + + @override + Future create(WalletAddress walletAddress) async { + await _box.put( + '${walletAddress.walletId}_${walletAddress.address}', walletAddress); + } + + @override + Future update(String walletId, String address, + {WalletAddress updateFields}) async { + final key = '${walletId}_$address'; + final existingWalletAddress = _box.get(key); + + if (existingWalletAddress == null) { + throw Exception('WalletAddress not found'); + } + + if (updateFields != null) { + final updatedWalletAddress = WalletAddress( + walletId: walletId, + address: address, + ticker: updateFields.ticker ?? existingWalletAddress.ticker, + availableBalance: updateFields.availableBalance ?? + existingWalletAddress.availableBalance, + accountId: updateFields.accountId ?? existingWalletAddress.accountId, + ); + + await _box.put(key, updatedWalletAddress); + } + } + + @override + Future deleteOne(String walletId, String address) async { + await _box.delete('${walletId}_$address'); + } + + @override + Future deleteAll(String walletId) async { + final keys = _box.keys.where((key) => key.startsWith(walletId)); + await _box.deleteAll(keys); + } + + @override + Future readOne(String walletId, String address) async { + return _box.get('${walletId}_$address'); + } + + @override + Future> readAll(String walletId) async { + return _box.values + .where((walletAddress) => walletAddress.walletId == walletId) + .toList(); + } + + @override + Stream watchAll(String walletId) { + return _box + .watch() + .where((event) => event.key.startsWith(walletId)) + .map((event) => event.value); + } +} From 0b25c304d7a8da4df949cac84af65220bba49ac9 Mon Sep 17 00:00:00 2001 From: naezith Date: Tue, 27 Jun 2023 12:42:19 +0300 Subject: [PATCH 04/24] validation and cleanup for AccountAddressesApiHive --- .../api/account_addresses_api_hive.dart | 27 ++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/lib/packages/account_addresses/api/account_addresses_api_hive.dart b/lib/packages/account_addresses/api/account_addresses_api_hive.dart index 27e5ab46b..77cd18efe 100644 --- a/lib/packages/account_addresses/api/account_addresses_api_hive.dart +++ b/lib/packages/account_addresses/api/account_addresses_api_hive.dart @@ -8,11 +8,17 @@ class AccountAddressesApiHive implements AccountAddressesApiInterface { AccountAddressesApiHive() { Hive.registerAdapter(WalletAddressAdapter()); - Hive.openBox('account_addresses').then((box) => _box = box); + Hive.openBox('account_addresses') + .then((box) => _box = box) + .onError((error, stackTrace) { + throw Exception('Error when opening the box: $error'); + }); } @override Future create(WalletAddress walletAddress) async { + _validateWalletAddress(walletAddress); + await _box.put( '${walletAddress.walletId}_${walletAddress.address}', walletAddress); } @@ -28,6 +34,8 @@ class AccountAddressesApiHive implements AccountAddressesApiInterface { } if (updateFields != null) { + _validateWalletAddress(updateFields); + final updatedWalletAddress = WalletAddress( walletId: walletId, address: address, @@ -71,4 +79,21 @@ class AccountAddressesApiHive implements AccountAddressesApiInterface { .where((event) => event.key.startsWith(walletId)) .map((event) => event.value); } + + Future close() async { + await _box.close(); + } + + void _validateWalletAddress(WalletAddress walletAddress) { + if (walletAddress.walletId == null || walletAddress.walletId.isEmpty) { + throw ArgumentError('Wallet ID must not be empty'); + } + if (walletAddress.address == null || walletAddress.address.isEmpty) { + throw ArgumentError('Address must not be empty'); + } + if (walletAddress.availableBalance == null || + walletAddress.availableBalance < 0) { + throw ArgumentError('Available balance must be non-negative'); + } + } } From 3cf3d9e816b728740d5fed0ed76cc20327850bdb Mon Sep 17 00:00:00 2001 From: naezith Date: Tue, 27 Jun 2023 12:44:01 +0300 Subject: [PATCH 05/24] error handling for AccountAddressesApiHive --- .../api/account_addresses_api_hive.dart | 63 ++++++++++++++----- 1 file changed, 47 insertions(+), 16 deletions(-) diff --git a/lib/packages/account_addresses/api/account_addresses_api_hive.dart b/lib/packages/account_addresses/api/account_addresses_api_hive.dart index 77cd18efe..9b1e86181 100644 --- a/lib/packages/account_addresses/api/account_addresses_api_hive.dart +++ b/lib/packages/account_addresses/api/account_addresses_api_hive.dart @@ -8,19 +8,26 @@ class AccountAddressesApiHive implements AccountAddressesApiInterface { AccountAddressesApiHive() { Hive.registerAdapter(WalletAddressAdapter()); - Hive.openBox('account_addresses') - .then((box) => _box = box) - .onError((error, stackTrace) { - throw Exception('Error when opening the box: $error'); - }); + _initializeBox(); + } + + Future _initializeBox() async { + try { + _box = await Hive.openBox('account_addresses'); + } catch (e) { + throw 'Failed to open Hive box: $e'; + } } @override Future create(WalletAddress walletAddress) async { _validateWalletAddress(walletAddress); - - await _box.put( - '${walletAddress.walletId}_${walletAddress.address}', walletAddress); + try { + await _box.put( + '${walletAddress.walletId}_${walletAddress.address}', walletAddress); + } catch (e) { + throw 'Failed to create WalletAddress: $e'; + } } @override @@ -45,31 +52,51 @@ class AccountAddressesApiHive implements AccountAddressesApiInterface { accountId: updateFields.accountId ?? existingWalletAddress.accountId, ); - await _box.put(key, updatedWalletAddress); + try { + await _box.put(key, updatedWalletAddress); + } catch (e) { + throw 'Failed to update WalletAddress: $e'; + } } } @override Future deleteOne(String walletId, String address) async { - await _box.delete('${walletId}_$address'); + try { + await _box.delete('${walletId}_$address'); + } catch (e) { + throw 'Failed to delete WalletAddress: $e'; + } } @override Future deleteAll(String walletId) async { final keys = _box.keys.where((key) => key.startsWith(walletId)); - await _box.deleteAll(keys); + try { + await _box.deleteAll(keys); + } catch (e) { + throw 'Failed to delete WalletAddresses: $e'; + } } @override Future readOne(String walletId, String address) async { - return _box.get('${walletId}_$address'); + try { + return _box.get('${walletId}_$address'); + } catch (e) { + throw 'Failed to read WalletAddress: $e'; + } } @override Future> readAll(String walletId) async { - return _box.values - .where((walletAddress) => walletAddress.walletId == walletId) - .toList(); + try { + return _box.values + .where((walletAddress) => walletAddress.walletId == walletId) + .toList(); + } catch (e) { + throw 'Failed to read WalletAddresses: $e'; + } } @override @@ -81,7 +108,11 @@ class AccountAddressesApiHive implements AccountAddressesApiInterface { } Future close() async { - await _box.close(); + try { + await _box.close(); + } catch (e) { + throw 'Failed to close Hive box: $e'; + } } void _validateWalletAddress(WalletAddress walletAddress) { From 01f5cab9d585e83b41f5c96146f2d097019c6fd0 Mon Sep 17 00:00:00 2001 From: naezith Date: Tue, 27 Jun 2023 18:14:45 +0300 Subject: [PATCH 06/24] field params instead of WalletAddress obj --- .../api/account_addresses_api_hive.dart | 66 +++++++++++-------- .../api/account_addresses_api_interface.dart | 5 +- 2 files changed, 40 insertions(+), 31 deletions(-) diff --git a/lib/packages/account_addresses/api/account_addresses_api_hive.dart b/lib/packages/account_addresses/api/account_addresses_api_hive.dart index 9b1e86181..65607f21d 100644 --- a/lib/packages/account_addresses/api/account_addresses_api_hive.dart +++ b/lib/packages/account_addresses/api/account_addresses_api_hive.dart @@ -11,6 +11,10 @@ class AccountAddressesApiHive implements AccountAddressesApiInterface { _initializeBox(); } + String _getKey(String walletId, String address) { + return '${walletId}_$address'; + } + Future _initializeBox() async { try { _box = await Hive.openBox('account_addresses'); @@ -20,11 +24,18 @@ class AccountAddressesApiHive implements AccountAddressesApiInterface { } @override - Future create(WalletAddress walletAddress) async { - _validateWalletAddress(walletAddress); + Future create(String walletId, String address, String ticker, + double availableBalance, String accountId) async { + _validateFields(walletId, address, availableBalance); try { await _box.put( - '${walletAddress.walletId}_${walletAddress.address}', walletAddress); + _getKey(walletId, address), + WalletAddress( + walletId: walletId, + address: address, + ticker: ticker, + availableBalance: availableBalance, + accountId: accountId)); } catch (e) { throw 'Failed to create WalletAddress: $e'; } @@ -32,38 +43,35 @@ class AccountAddressesApiHive implements AccountAddressesApiInterface { @override Future update(String walletId, String address, - {WalletAddress updateFields}) async { - final key = '${walletId}_$address'; + {String ticker, double availableBalance, String accountId}) async { + _validateFields(walletId, address, availableBalance); + final key = _getKey(walletId, address); final existingWalletAddress = _box.get(key); if (existingWalletAddress == null) { throw Exception('WalletAddress not found'); } - if (updateFields != null) { - _validateWalletAddress(updateFields); - - final updatedWalletAddress = WalletAddress( - walletId: walletId, - address: address, - ticker: updateFields.ticker ?? existingWalletAddress.ticker, - availableBalance: updateFields.availableBalance ?? - existingWalletAddress.availableBalance, - accountId: updateFields.accountId ?? existingWalletAddress.accountId, - ); - - try { - await _box.put(key, updatedWalletAddress); - } catch (e) { - throw 'Failed to update WalletAddress: $e'; - } + final updatedWalletAddress = WalletAddress( + walletId: walletId, + address: address, + ticker: ticker ?? existingWalletAddress.ticker, + availableBalance: + availableBalance ?? existingWalletAddress.availableBalance, + accountId: accountId ?? existingWalletAddress.accountId, + ); + + try { + await _box.put(key, updatedWalletAddress); + } catch (e) { + throw 'Failed to update WalletAddress: $e'; } } @override Future deleteOne(String walletId, String address) async { try { - await _box.delete('${walletId}_$address'); + await _box.delete(_getKey(walletId, address)); } catch (e) { throw 'Failed to delete WalletAddress: $e'; } @@ -82,7 +90,7 @@ class AccountAddressesApiHive implements AccountAddressesApiInterface { @override Future readOne(String walletId, String address) async { try { - return _box.get('${walletId}_$address'); + return _box.get(_getKey(walletId, address)); } catch (e) { throw 'Failed to read WalletAddress: $e'; } @@ -115,15 +123,15 @@ class AccountAddressesApiHive implements AccountAddressesApiInterface { } } - void _validateWalletAddress(WalletAddress walletAddress) { - if (walletAddress.walletId == null || walletAddress.walletId.isEmpty) { + void _validateFields( + String walletId, String address, double availableBalance) { + if (walletId == null || walletId.isEmpty) { throw ArgumentError('Wallet ID must not be empty'); } - if (walletAddress.address == null || walletAddress.address.isEmpty) { + if (address == null || address.isEmpty) { throw ArgumentError('Address must not be empty'); } - if (walletAddress.availableBalance == null || - walletAddress.availableBalance < 0) { + if (availableBalance == null || availableBalance < 0) { throw ArgumentError('Available balance must be non-negative'); } } diff --git a/lib/packages/account_addresses/api/account_addresses_api_interface.dart b/lib/packages/account_addresses/api/account_addresses_api_interface.dart index 26851b997..63b6ff0e9 100644 --- a/lib/packages/account_addresses/api/account_addresses_api_interface.dart +++ b/lib/packages/account_addresses/api/account_addresses_api_interface.dart @@ -1,9 +1,10 @@ import 'package:komodo_dex/packages/account_addresses/models/wallet_address.dart'; abstract class AccountAddressesApiInterface { - Future create(WalletAddress walletAddress); + Future create(String walletId, String address, String ticker, + double availableBalance, String accountId); Future update(String walletId, String address, - {WalletAddress updateFields}); + {String ticker, double availableBalance, String accountId}); Future deleteOne(String walletId, String address); Future deleteAll(String walletId); Future readOne(String walletId, String address); From 90b154111f4901aebfb987d6ca4aebca06b82e38 Mon Sep 17 00:00:00 2001 From: naezith Date: Tue, 27 Jun 2023 18:22:41 +0300 Subject: [PATCH 07/24] updateOrCreate for AccountAddressesApiHive --- .../api/account_addresses_api_hive.dart | 14 ++++++++++++++ .../api/account_addresses_api_interface.dart | 2 ++ 2 files changed, 16 insertions(+) diff --git a/lib/packages/account_addresses/api/account_addresses_api_hive.dart b/lib/packages/account_addresses/api/account_addresses_api_hive.dart index 65607f21d..2a0f287d8 100644 --- a/lib/packages/account_addresses/api/account_addresses_api_hive.dart +++ b/lib/packages/account_addresses/api/account_addresses_api_hive.dart @@ -41,6 +41,20 @@ class AccountAddressesApiHive implements AccountAddressesApiInterface { } } + @override + Future updateOrCreate(String walletId, String address, String ticker, + double availableBalance, String accountId) async { + final key = _getKey(walletId, address); + if (_box.containsKey(key)) { + await update(walletId, address, + ticker: ticker, + availableBalance: availableBalance, + accountId: accountId); + } else { + await create(walletId, address, ticker, availableBalance, accountId); + } + } + @override Future update(String walletId, String address, {String ticker, double availableBalance, String accountId}) async { diff --git a/lib/packages/account_addresses/api/account_addresses_api_interface.dart b/lib/packages/account_addresses/api/account_addresses_api_interface.dart index 63b6ff0e9..bac918cea 100644 --- a/lib/packages/account_addresses/api/account_addresses_api_interface.dart +++ b/lib/packages/account_addresses/api/account_addresses_api_interface.dart @@ -5,6 +5,8 @@ abstract class AccountAddressesApiInterface { double availableBalance, String accountId); Future update(String walletId, String address, {String ticker, double availableBalance, String accountId}); + Future updateOrCreate(String walletId, String address, String ticker, + double availableBalance, String accountId); Future deleteOne(String walletId, String address); Future deleteAll(String walletId); Future readOne(String walletId, String address); From 14748a45bea4db74907cab292c8e719ee71853fd Mon Sep 17 00:00:00 2001 From: naezith Date: Tue, 27 Jun 2023 18:39:25 +0300 Subject: [PATCH 08/24] factory for AccountAddressesApiHive --- .../api/account_addresses_api_hive.dart | 27 ++++++++++--------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/lib/packages/account_addresses/api/account_addresses_api_hive.dart b/lib/packages/account_addresses/api/account_addresses_api_hive.dart index 2a0f287d8..8ab8e7b3b 100644 --- a/lib/packages/account_addresses/api/account_addresses_api_hive.dart +++ b/lib/packages/account_addresses/api/account_addresses_api_hive.dart @@ -4,25 +4,25 @@ import 'package:komodo_dex/packages/account_addresses/api/account_addresses_api_ import 'package:komodo_dex/packages/account_addresses/models/wallet_address.dart'; class AccountAddressesApiHive implements AccountAddressesApiInterface { - Box _box; + final Box _box; - AccountAddressesApiHive() { + AccountAddressesApiHive._(this._box); + + static Future initialize() async { Hive.registerAdapter(WalletAddressAdapter()); - _initializeBox(); + Box box; + try { + box = await Hive.openBox('account_addresses'); + } catch (e) { + throw FormatException('Failed to open Hive box: $e'); + } + return AccountAddressesApiHive._(box); } String _getKey(String walletId, String address) { return '${walletId}_$address'; } - Future _initializeBox() async { - try { - _box = await Hive.openBox('account_addresses'); - } catch (e) { - throw 'Failed to open Hive box: $e'; - } - } - @override Future create(String walletId, String address, String ticker, double availableBalance, String accountId) async { @@ -125,8 +125,9 @@ class AccountAddressesApiHive implements AccountAddressesApiInterface { Stream watchAll(String walletId) { return _box .watch() - .where((event) => event.key.startsWith(walletId)) - .map((event) => event.value); + .where((event) => + event.key.startsWith(walletId) && event.value is WalletAddress) + .map((event) => event.value as WalletAddress); } Future close() async { From cb92f7898135fc7edcc394a2c691e7c2039adc63 Mon Sep 17 00:00:00 2001 From: naezith Date: Tue, 27 Jun 2023 18:47:30 +0300 Subject: [PATCH 09/24] HiveException for AccountAddressesApiHive --- .../api/account_addresses_api_hive.dart | 23 +++++++++++++------ 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/lib/packages/account_addresses/api/account_addresses_api_hive.dart b/lib/packages/account_addresses/api/account_addresses_api_hive.dart index 8ab8e7b3b..f38765d45 100644 --- a/lib/packages/account_addresses/api/account_addresses_api_hive.dart +++ b/lib/packages/account_addresses/api/account_addresses_api_hive.dart @@ -3,6 +3,15 @@ import 'package:hive_flutter/hive_flutter.dart'; import 'package:komodo_dex/packages/account_addresses/api/account_addresses_api_interface.dart'; import 'package:komodo_dex/packages/account_addresses/models/wallet_address.dart'; +class HiveException implements Exception { + final String message; + + HiveException(this.message); + + @override + String toString() => 'HiveException: $message'; +} + class AccountAddressesApiHive implements AccountAddressesApiInterface { final Box _box; @@ -37,7 +46,7 @@ class AccountAddressesApiHive implements AccountAddressesApiInterface { availableBalance: availableBalance, accountId: accountId)); } catch (e) { - throw 'Failed to create WalletAddress: $e'; + throw HiveException('Failed to create WalletAddress: $e'); } } @@ -78,7 +87,7 @@ class AccountAddressesApiHive implements AccountAddressesApiInterface { try { await _box.put(key, updatedWalletAddress); } catch (e) { - throw 'Failed to update WalletAddress: $e'; + throw HiveException('Failed to update WalletAddress: $e'); } } @@ -87,7 +96,7 @@ class AccountAddressesApiHive implements AccountAddressesApiInterface { try { await _box.delete(_getKey(walletId, address)); } catch (e) { - throw 'Failed to delete WalletAddress: $e'; + throw HiveException('Failed to delete WalletAddress: $e'); } } @@ -97,7 +106,7 @@ class AccountAddressesApiHive implements AccountAddressesApiInterface { try { await _box.deleteAll(keys); } catch (e) { - throw 'Failed to delete WalletAddresses: $e'; + throw HiveException('Failed to delete WalletAddresses: $e'); } } @@ -106,7 +115,7 @@ class AccountAddressesApiHive implements AccountAddressesApiInterface { try { return _box.get(_getKey(walletId, address)); } catch (e) { - throw 'Failed to read WalletAddress: $e'; + throw HiveException('Failed to read WalletAddress: $e'); } } @@ -117,7 +126,7 @@ class AccountAddressesApiHive implements AccountAddressesApiInterface { .where((walletAddress) => walletAddress.walletId == walletId) .toList(); } catch (e) { - throw 'Failed to read WalletAddresses: $e'; + throw HiveException('Failed to read WalletAddresses: $e'); } } @@ -134,7 +143,7 @@ class AccountAddressesApiHive implements AccountAddressesApiInterface { try { await _box.close(); } catch (e) { - throw 'Failed to close Hive box: $e'; + throw HiveException('Failed to close Hive box: $e'); } } From 562b899a5da7914769dd94a4c6323bbea1bbc1dc Mon Sep 17 00:00:00 2001 From: naezith Date: Tue, 27 Jun 2023 18:48:59 +0300 Subject: [PATCH 10/24] FutureOr for AccountAddressesApiHive init --- .../account_addresses/api/account_addresses_api_hive.dart | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/packages/account_addresses/api/account_addresses_api_hive.dart b/lib/packages/account_addresses/api/account_addresses_api_hive.dart index f38765d45..c8bf69c5b 100644 --- a/lib/packages/account_addresses/api/account_addresses_api_hive.dart +++ b/lib/packages/account_addresses/api/account_addresses_api_hive.dart @@ -1,3 +1,4 @@ +import 'dart:async'; import 'package:hive/hive.dart'; import 'package:hive_flutter/hive_flutter.dart'; import 'package:komodo_dex/packages/account_addresses/api/account_addresses_api_interface.dart'; @@ -17,13 +18,13 @@ class AccountAddressesApiHive implements AccountAddressesApiInterface { AccountAddressesApiHive._(this._box); - static Future initialize() async { + static FutureOr initialize() async { Hive.registerAdapter(WalletAddressAdapter()); Box box; try { box = await Hive.openBox('account_addresses'); } catch (e) { - throw FormatException('Failed to open Hive box: $e'); + throw HiveException('Failed to open Hive box: $e'); } return AccountAddressesApiHive._(box); } From dc8fa9037a64e6df728f6d31353d5684826bc2f9 Mon Sep 17 00:00:00 2001 From: naezith Date: Tue, 27 Jun 2023 18:55:28 +0300 Subject: [PATCH 11/24] store original exception in HiveException --- .../api/account_addresses_api_hive.dart | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/lib/packages/account_addresses/api/account_addresses_api_hive.dart b/lib/packages/account_addresses/api/account_addresses_api_hive.dart index c8bf69c5b..6f7e9a361 100644 --- a/lib/packages/account_addresses/api/account_addresses_api_hive.dart +++ b/lib/packages/account_addresses/api/account_addresses_api_hive.dart @@ -6,8 +6,9 @@ import 'package:komodo_dex/packages/account_addresses/models/wallet_address.dart class HiveException implements Exception { final String message; + final Exception originalException; - HiveException(this.message); + HiveException(this.message, [this.originalException]); @override String toString() => 'HiveException: $message'; @@ -24,7 +25,7 @@ class AccountAddressesApiHive implements AccountAddressesApiInterface { try { box = await Hive.openBox('account_addresses'); } catch (e) { - throw HiveException('Failed to open Hive box: $e'); + throw HiveException('Failed to open Hive box: $e', e); } return AccountAddressesApiHive._(box); } @@ -47,7 +48,7 @@ class AccountAddressesApiHive implements AccountAddressesApiInterface { availableBalance: availableBalance, accountId: accountId)); } catch (e) { - throw HiveException('Failed to create WalletAddress: $e'); + throw HiveException('Failed to create WalletAddress: $e', e); } } @@ -88,7 +89,7 @@ class AccountAddressesApiHive implements AccountAddressesApiInterface { try { await _box.put(key, updatedWalletAddress); } catch (e) { - throw HiveException('Failed to update WalletAddress: $e'); + throw HiveException('Failed to update WalletAddress: $e', e); } } @@ -97,7 +98,7 @@ class AccountAddressesApiHive implements AccountAddressesApiInterface { try { await _box.delete(_getKey(walletId, address)); } catch (e) { - throw HiveException('Failed to delete WalletAddress: $e'); + throw HiveException('Failed to delete WalletAddress: $e', e); } } @@ -107,7 +108,7 @@ class AccountAddressesApiHive implements AccountAddressesApiInterface { try { await _box.deleteAll(keys); } catch (e) { - throw HiveException('Failed to delete WalletAddresses: $e'); + throw HiveException('Failed to delete WalletAddresses: $e', e); } } @@ -116,7 +117,7 @@ class AccountAddressesApiHive implements AccountAddressesApiInterface { try { return _box.get(_getKey(walletId, address)); } catch (e) { - throw HiveException('Failed to read WalletAddress: $e'); + throw HiveException('Failed to read WalletAddress: $e', e); } } @@ -127,7 +128,7 @@ class AccountAddressesApiHive implements AccountAddressesApiInterface { .where((walletAddress) => walletAddress.walletId == walletId) .toList(); } catch (e) { - throw HiveException('Failed to read WalletAddresses: $e'); + throw HiveException('Failed to read WalletAddresses: $e', e); } } @@ -144,7 +145,7 @@ class AccountAddressesApiHive implements AccountAddressesApiInterface { try { await _box.close(); } catch (e) { - throw HiveException('Failed to close Hive box: $e'); + throw HiveException('Failed to close Hive box: $e', e); } } From 448162ad03ccaf57facf086d44114623454fa016 Mon Sep 17 00:00:00 2001 From: naezith Date: Tue, 27 Jun 2023 19:08:28 +0300 Subject: [PATCH 12/24] remove comment --- lib/packages/account_addresses/models/wallet_address.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/packages/account_addresses/models/wallet_address.dart b/lib/packages/account_addresses/models/wallet_address.dart index 8e8320bbd..d5cc15b94 100644 --- a/lib/packages/account_addresses/models/wallet_address.dart +++ b/lib/packages/account_addresses/models/wallet_address.dart @@ -1,6 +1,6 @@ import 'package:hive/hive.dart'; -part 'wallet_address.g.dart'; // Name of the TypeAdapter generated by Hive +part 'wallet_address.g.dart'; @HiveType(typeId: 0) class WalletAddress { From 16bb0cac7bde10daa94d5014e10362c5a4c7c5aa Mon Sep 17 00:00:00 2001 From: naezith Date: Wed, 28 Jun 2023 13:22:20 +0300 Subject: [PATCH 13/24] call Hive.registerAdapter only once --- .../account_addresses/api/account_addresses_api_hive.dart | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/lib/packages/account_addresses/api/account_addresses_api_hive.dart b/lib/packages/account_addresses/api/account_addresses_api_hive.dart index 6f7e9a361..e20a27a1c 100644 --- a/lib/packages/account_addresses/api/account_addresses_api_hive.dart +++ b/lib/packages/account_addresses/api/account_addresses_api_hive.dart @@ -17,10 +17,16 @@ class HiveException implements Exception { class AccountAddressesApiHive implements AccountAddressesApiInterface { final Box _box; + static bool _isAdapterRegistered = false; + AccountAddressesApiHive._(this._box); static FutureOr initialize() async { - Hive.registerAdapter(WalletAddressAdapter()); + if (!_isAdapterRegistered) { + Hive.registerAdapter(WalletAddressAdapter()); + _isAdapterRegistered = true; + } + Box box; try { box = await Hive.openBox('account_addresses'); From 8d7d6ee31aae10eae2d7d2afa2d5e5faa83ae12c Mon Sep 17 00:00:00 2001 From: naezith Date: Wed, 28 Jun 2023 13:27:34 +0300 Subject: [PATCH 14/24] add trailing commas --- .../api/account_addresses_api_hive.dart | 65 +++++++++++++------ .../api/account_addresses_api_interface.dart | 27 ++++++-- 2 files changed, 66 insertions(+), 26 deletions(-) diff --git a/lib/packages/account_addresses/api/account_addresses_api_hive.dart b/lib/packages/account_addresses/api/account_addresses_api_hive.dart index e20a27a1c..cc7c6af85 100644 --- a/lib/packages/account_addresses/api/account_addresses_api_hive.dart +++ b/lib/packages/account_addresses/api/account_addresses_api_hive.dart @@ -41,40 +41,60 @@ class AccountAddressesApiHive implements AccountAddressesApiInterface { } @override - Future create(String walletId, String address, String ticker, - double availableBalance, String accountId) async { + Future create( + String walletId, + String address, + String ticker, + double availableBalance, + String accountId, + ) async { _validateFields(walletId, address, availableBalance); try { await _box.put( - _getKey(walletId, address), - WalletAddress( - walletId: walletId, - address: address, - ticker: ticker, - availableBalance: availableBalance, - accountId: accountId)); + _getKey(walletId, address), + WalletAddress( + walletId: walletId, + address: address, + ticker: ticker, + availableBalance: availableBalance, + accountId: accountId, + ), + ); } catch (e) { throw HiveException('Failed to create WalletAddress: $e', e); } } @override - Future updateOrCreate(String walletId, String address, String ticker, - double availableBalance, String accountId) async { + Future updateOrCreate( + String walletId, + String address, + String ticker, + double availableBalance, + String accountId, + ) async { final key = _getKey(walletId, address); if (_box.containsKey(key)) { - await update(walletId, address, - ticker: ticker, - availableBalance: availableBalance, - accountId: accountId); + await update( + walletId, + address, + ticker: ticker, + availableBalance: availableBalance, + accountId: accountId, + ); } else { await create(walletId, address, ticker, availableBalance, accountId); } } @override - Future update(String walletId, String address, - {String ticker, double availableBalance, String accountId}) async { + Future update( + String walletId, + String address, { + String ticker, + double availableBalance, + String accountId, + }) async { _validateFields(walletId, address, availableBalance); final key = _getKey(walletId, address); final existingWalletAddress = _box.get(key); @@ -142,8 +162,10 @@ class AccountAddressesApiHive implements AccountAddressesApiInterface { Stream watchAll(String walletId) { return _box .watch() - .where((event) => - event.key.startsWith(walletId) && event.value is WalletAddress) + .where( + (event) => + event.key.startsWith(walletId) && event.value is WalletAddress, + ) .map((event) => event.value as WalletAddress); } @@ -156,7 +178,10 @@ class AccountAddressesApiHive implements AccountAddressesApiInterface { } void _validateFields( - String walletId, String address, double availableBalance) { + String walletId, + String address, + double availableBalance, + ) { if (walletId == null || walletId.isEmpty) { throw ArgumentError('Wallet ID must not be empty'); } diff --git a/lib/packages/account_addresses/api/account_addresses_api_interface.dart b/lib/packages/account_addresses/api/account_addresses_api_interface.dart index bac918cea..e439e186c 100644 --- a/lib/packages/account_addresses/api/account_addresses_api_interface.dart +++ b/lib/packages/account_addresses/api/account_addresses_api_interface.dart @@ -1,12 +1,27 @@ import 'package:komodo_dex/packages/account_addresses/models/wallet_address.dart'; abstract class AccountAddressesApiInterface { - Future create(String walletId, String address, String ticker, - double availableBalance, String accountId); - Future update(String walletId, String address, - {String ticker, double availableBalance, String accountId}); - Future updateOrCreate(String walletId, String address, String ticker, - double availableBalance, String accountId); + Future create( + String walletId, + String address, + String ticker, + double availableBalance, + String accountId, + ); + Future update( + String walletId, + String address, { + String ticker, + double availableBalance, + String accountId, + }); + Future updateOrCreate( + String walletId, + String address, + String ticker, + double availableBalance, + String accountId, + ); Future deleteOne(String walletId, String address); Future deleteAll(String walletId); Future readOne(String walletId, String address); From b0b6fa7fbce61b0afc44e8e34d2214e806ff4346 Mon Sep 17 00:00:00 2001 From: naezith Date: Wed, 28 Jun 2023 13:40:46 +0300 Subject: [PATCH 15/24] added @required and named params --- .../api/account_addresses_api_hive.dart | 63 ++++++++++-------- .../api/account_addresses_api_interface.dart | 64 ++++++++++++------- .../models/wallet_address.dart | 21 +++--- 3 files changed, 91 insertions(+), 57 deletions(-) diff --git a/lib/packages/account_addresses/api/account_addresses_api_hive.dart b/lib/packages/account_addresses/api/account_addresses_api_hive.dart index cc7c6af85..b76766ea5 100644 --- a/lib/packages/account_addresses/api/account_addresses_api_hive.dart +++ b/lib/packages/account_addresses/api/account_addresses_api_hive.dart @@ -3,6 +3,7 @@ import 'package:hive/hive.dart'; import 'package:hive_flutter/hive_flutter.dart'; import 'package:komodo_dex/packages/account_addresses/api/account_addresses_api_interface.dart'; import 'package:komodo_dex/packages/account_addresses/models/wallet_address.dart'; +import 'package:meta/meta.dart'; class HiveException implements Exception { final String message; @@ -41,13 +42,13 @@ class AccountAddressesApiHive implements AccountAddressesApiInterface { } @override - Future create( - String walletId, - String address, - String ticker, - double availableBalance, - String accountId, - ) async { + Future create({ + @required String walletId, + @required String address, + @required String ticker, + @required double availableBalance, + @required String accountId, + }) async { _validateFields(walletId, address, availableBalance); try { await _box.put( @@ -66,31 +67,37 @@ class AccountAddressesApiHive implements AccountAddressesApiInterface { } @override - Future updateOrCreate( - String walletId, - String address, - String ticker, - double availableBalance, - String accountId, - ) async { + Future updateOrCreate({ + @required String walletId, + @required String address, + @required String ticker, + @required double availableBalance, + @required String accountId, + }) async { final key = _getKey(walletId, address); if (_box.containsKey(key)) { await update( - walletId, - address, + walletId: walletId, + address: address, ticker: ticker, availableBalance: availableBalance, accountId: accountId, ); } else { - await create(walletId, address, ticker, availableBalance, accountId); + await create( + walletId: walletId, + address: address, + ticker: ticker, + availableBalance: availableBalance, + accountId: accountId, + ); } } @override - Future update( - String walletId, - String address, { + Future update({ + @required String walletId, + @required String address, String ticker, double availableBalance, String accountId, @@ -120,7 +127,10 @@ class AccountAddressesApiHive implements AccountAddressesApiInterface { } @override - Future deleteOne(String walletId, String address) async { + Future deleteOne({ + @required String walletId, + @required String address, + }) async { try { await _box.delete(_getKey(walletId, address)); } catch (e) { @@ -129,7 +139,7 @@ class AccountAddressesApiHive implements AccountAddressesApiInterface { } @override - Future deleteAll(String walletId) async { + Future deleteAll({@required String walletId}) async { final keys = _box.keys.where((key) => key.startsWith(walletId)); try { await _box.deleteAll(keys); @@ -139,7 +149,10 @@ class AccountAddressesApiHive implements AccountAddressesApiInterface { } @override - Future readOne(String walletId, String address) async { + Future readOne({ + @required String walletId, + @required String address, + }) async { try { return _box.get(_getKey(walletId, address)); } catch (e) { @@ -148,7 +161,7 @@ class AccountAddressesApiHive implements AccountAddressesApiInterface { } @override - Future> readAll(String walletId) async { + Future> readAll({@required String walletId}) async { try { return _box.values .where((walletAddress) => walletAddress.walletId == walletId) @@ -159,7 +172,7 @@ class AccountAddressesApiHive implements AccountAddressesApiInterface { } @override - Stream watchAll(String walletId) { + Stream watchAll({@required String walletId}) { return _box .watch() .where( diff --git a/lib/packages/account_addresses/api/account_addresses_api_interface.dart b/lib/packages/account_addresses/api/account_addresses_api_interface.dart index e439e186c..2422ddd94 100644 --- a/lib/packages/account_addresses/api/account_addresses_api_interface.dart +++ b/lib/packages/account_addresses/api/account_addresses_api_interface.dart @@ -1,30 +1,50 @@ +import 'package:meta/meta.dart'; import 'package:komodo_dex/packages/account_addresses/models/wallet_address.dart'; abstract class AccountAddressesApiInterface { - Future create( - String walletId, - String address, - String ticker, - double availableBalance, - String accountId, - ); - Future update( - String walletId, - String address, { - String ticker, - double availableBalance, - String accountId, + Future create({ + @required String walletId, + @required String address, + @required String ticker, + @required double availableBalance, + @required String accountId, }); - Future updateOrCreate( - String walletId, - String address, + + Future update({ + @required String walletId, + @required String address, String ticker, double availableBalance, String accountId, - ); - Future deleteOne(String walletId, String address); - Future deleteAll(String walletId); - Future readOne(String walletId, String address); - Future> readAll(String walletId); - Stream watchAll(String walletId); + }); + + Future updateOrCreate({ + @required String walletId, + @required String address, + @required String ticker, + @required double availableBalance, + @required String accountId, + }); + + Future deleteOne({ + @required String walletId, + @required String address, + }); + + Future deleteAll({ + @required String walletId, + }); + + Future readOne({ + @required String walletId, + @required String address, + }); + + Future> readAll({ + @required String walletId, + }); + + Stream watchAll({ + @required String walletId, + }); } diff --git a/lib/packages/account_addresses/models/wallet_address.dart b/lib/packages/account_addresses/models/wallet_address.dart index d5cc15b94..75a37d89c 100644 --- a/lib/packages/account_addresses/models/wallet_address.dart +++ b/lib/packages/account_addresses/models/wallet_address.dart @@ -1,3 +1,4 @@ +import 'package:meta/meta.dart'; import 'package:hive/hive.dart'; part 'wallet_address.g.dart'; @@ -5,25 +6,25 @@ part 'wallet_address.g.dart'; @HiveType(typeId: 0) class WalletAddress { @HiveField(0) - String walletId; + final String walletId; @HiveField(1) - String address; + final String address; @HiveField(2) - String ticker; + final String ticker; @HiveField(3) - double availableBalance; + final double availableBalance; @HiveField(4) - String accountId; + final String accountId; WalletAddress({ - this.walletId, - this.address, - this.ticker, - this.availableBalance, - this.accountId, + @required this.walletId, + @required this.address, + @required this.ticker, + @required this.availableBalance, + @required this.accountId, }); } From f5809ab11effa0708fe14d989269adbc197ef039 Mon Sep 17 00:00:00 2001 From: naezith Date: Wed, 28 Jun 2023 15:11:09 +0300 Subject: [PATCH 16/24] implement AccountAddressesRepository --- .../account_addresses_repository.dart | 86 +++++++++++++++++++ 1 file changed, 86 insertions(+) create mode 100644 lib/packages/account_addresses/repository/account_addresses_repository.dart diff --git a/lib/packages/account_addresses/repository/account_addresses_repository.dart b/lib/packages/account_addresses/repository/account_addresses_repository.dart new file mode 100644 index 000000000..f4463252e --- /dev/null +++ b/lib/packages/account_addresses/repository/account_addresses_repository.dart @@ -0,0 +1,86 @@ +import 'dart:async'; +import 'dart:convert'; +import 'package:komodo_dex/model/coin_balance.dart'; +import 'package:komodo_dex/packages/account_addresses/api/account_addresses_api_interface.dart'; +import 'package:komodo_dex/packages/account_addresses/models/wallet_address.dart'; +import 'package:komodo_dex/services/db/database.dart'; +import 'package:meta/meta.dart'; + +class AccountAddressesRepository { + final AccountAddressesApiInterface _accountAddressesApi; + String _currentWalletId; + StreamController _controller; + StreamSubscription _watchSubscription; + bool _isDisposed = false; + + AccountAddressesRepository({ + @required AccountAddressesApiInterface accountAddressesApi, + }) : _accountAddressesApi = accountAddressesApi { + _controller = StreamController(); + _startPolling(); + } + + Future _startPolling() async { + while (!_isDisposed) { + try { + final currentWallet = await Db.getCurrentWallet(); + if (currentWallet != null && currentWallet.id != _currentWalletId) { + _currentWalletId = currentWallet.id; + + _watchSubscription?.cancel(); + _watchSubscription = _accountAddressesApi + .watchAll(walletId: _currentWalletId) + .listen((WalletAddress walletAddress) async { + _controller.add(walletAddress); + }); + + final String jsonStr = await Db.getWalletSnapshot(); + if (jsonStr != null) { + List items; + try { + items = json.decode(jsonStr); + } catch (e) { + throw Exception('Failed to decode getWalletSnapshot JSON: $e'); + } + + if (items != null) { + for (final item in items) { + final coinBalance = CoinBalance.fromJson(item); + + final walletAddress = WalletAddress( + walletId: _currentWalletId, + address: coinBalance.balance.address, + ticker: coinBalance.coin.abbr, + availableBalance: coinBalance.balance.balance.toDouble(), + accountId: 'iguana', + ); + + await _accountAddressesApi.updateOrCreate( + walletId: walletAddress.walletId, + address: walletAddress.address, + ticker: walletAddress.ticker, + availableBalance: walletAddress.availableBalance, + accountId: walletAddress.accountId, + ); + } + } + } + } + } catch (e) { + _controller.addError(e); + } + + await Future.delayed(Duration(seconds: 5)); + } + } + + Stream watchCurrentWalletAddresses() async* { + yield* _controller.stream; + } + + void dispose() { + _isDisposed = true; + _watchSubscription?.cancel(); + _controller.close(); + } +} From 1b3066b2d4b33d967cd0122f0ea37835f948df10 Mon Sep 17 00:00:00 2001 From: naezith Date: Fri, 30 Jun 2023 01:13:01 +0300 Subject: [PATCH 17/24] atomic AccountAddressesRepository --- .../account_addresses_repository.dart | 118 +++++++++--------- 1 file changed, 58 insertions(+), 60 deletions(-) diff --git a/lib/packages/account_addresses/repository/account_addresses_repository.dart b/lib/packages/account_addresses/repository/account_addresses_repository.dart index f4463252e..a55926007 100644 --- a/lib/packages/account_addresses/repository/account_addresses_repository.dart +++ b/lib/packages/account_addresses/repository/account_addresses_repository.dart @@ -8,79 +8,77 @@ import 'package:meta/meta.dart'; class AccountAddressesRepository { final AccountAddressesApiInterface _accountAddressesApi; - String _currentWalletId; - StreamController _controller; - StreamSubscription _watchSubscription; - bool _isDisposed = false; AccountAddressesRepository({ @required AccountAddressesApiInterface accountAddressesApi, - }) : _accountAddressesApi = accountAddressesApi { - _controller = StreamController(); - _startPolling(); - } + }) : _accountAddressesApi = accountAddressesApi; - Future _startPolling() async { - while (!_isDisposed) { - try { - final currentWallet = await Db.getCurrentWallet(); - if (currentWallet != null && currentWallet.id != _currentWalletId) { - _currentWalletId = currentWallet.id; + Stream watchCurrentWalletAddresses() async* { + StreamController controller; + StreamSubscription subscription; - _watchSubscription?.cancel(); - _watchSubscription = _accountAddressesApi - .watchAll(walletId: _currentWalletId) - .listen((WalletAddress walletAddress) async { - _controller.add(walletAddress); - }); + controller = StreamController( + onListen: () async { + String currentWalletId; - final String jsonStr = await Db.getWalletSnapshot(); - if (jsonStr != null) { - List items; - try { - items = json.decode(jsonStr); - } catch (e) { - throw Exception('Failed to decode getWalletSnapshot JSON: $e'); - } + while (controller.hasListener) { + final currentWallet = await Db.getCurrentWallet(); + if (currentWallet != null && currentWallet.id != currentWalletId) { + currentWalletId = currentWallet.id; - if (items != null) { - for (final item in items) { - final coinBalance = CoinBalance.fromJson(item); + // Emit the switched wallet information + final String jsonStr = await Db.getWalletSnapshot(); + if (jsonStr != null) { + List items; + try { + items = json.decode(jsonStr); + } catch (e) { + controller + .addError('Failed to decode getWalletSnapshot JSON: $e'); + } - final walletAddress = WalletAddress( - walletId: _currentWalletId, - address: coinBalance.balance.address, - ticker: coinBalance.coin.abbr, - availableBalance: coinBalance.balance.balance.toDouble(), - accountId: 'iguana', - ); + if (items != null) { + for (final item in items) { + final coinBalance = CoinBalance.fromJson(item); - await _accountAddressesApi.updateOrCreate( - walletId: walletAddress.walletId, - address: walletAddress.address, - ticker: walletAddress.ticker, - availableBalance: walletAddress.availableBalance, - accountId: walletAddress.accountId, - ); + final walletAddress = WalletAddress( + walletId: currentWalletId, + address: coinBalance.balance.address, + ticker: coinBalance.coin.abbr, + availableBalance: coinBalance.balance.balance.toDouble(), + accountId: 'iguana', + ); + + await _accountAddressesApi.updateOrCreate( + walletId: walletAddress.walletId, + address: walletAddress.address, + ticker: walletAddress.ticker, + availableBalance: walletAddress.availableBalance, + accountId: walletAddress.accountId, + ); + + controller.add(walletAddress); + } } } - } - } - } catch (e) { - _controller.addError(e); - } - await Future.delayed(Duration(seconds: 5)); - } - } + // Listen and emit newer changes that will occur + await subscription?.cancel(); + subscription = _accountAddressesApi + .watchAll(walletId: currentWalletId) + .listen((updatedAddress) { + controller.add(updatedAddress); + }); + } - Stream watchCurrentWalletAddresses() async* { - yield* _controller.stream; - } + await Future.delayed(Duration(seconds: 5)); + } + }, + onCancel: () async { + await subscription?.cancel(); + }, + ); - void dispose() { - _isDisposed = true; - _watchSubscription?.cancel(); - _controller.close(); + yield* controller.stream; } } From 010fcc5180c31d01352f29a864683e41b67d6c4b Mon Sep 17 00:00:00 2001 From: naezith Date: Fri, 30 Jun 2023 01:19:14 +0300 Subject: [PATCH 18/24] cancel AddressRepository subscription earlier --- .../repository/account_addresses_repository.dart | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/packages/account_addresses/repository/account_addresses_repository.dart b/lib/packages/account_addresses/repository/account_addresses_repository.dart index a55926007..2e7c99a19 100644 --- a/lib/packages/account_addresses/repository/account_addresses_repository.dart +++ b/lib/packages/account_addresses/repository/account_addresses_repository.dart @@ -26,6 +26,8 @@ class AccountAddressesRepository { if (currentWallet != null && currentWallet.id != currentWalletId) { currentWalletId = currentWallet.id; + await subscription?.cancel(); + // Emit the switched wallet information final String jsonStr = await Db.getWalletSnapshot(); if (jsonStr != null) { @@ -63,7 +65,6 @@ class AccountAddressesRepository { } // Listen and emit newer changes that will occur - await subscription?.cancel(); subscription = _accountAddressesApi .watchAll(walletId: currentWalletId) .listen((updatedAddress) { From 3ea1e55cffd598cfd6ee32b5ffad976a4ef75983 Mon Sep 17 00:00:00 2001 From: naezith Date: Mon, 3 Jul 2023 09:46:07 +0300 Subject: [PATCH 19/24] remove custom HiveException --- .../api/account_addresses_api_hive.dart | 26 ++++++------------- 1 file changed, 8 insertions(+), 18 deletions(-) diff --git a/lib/packages/account_addresses/api/account_addresses_api_hive.dart b/lib/packages/account_addresses/api/account_addresses_api_hive.dart index b76766ea5..05a30bf1c 100644 --- a/lib/packages/account_addresses/api/account_addresses_api_hive.dart +++ b/lib/packages/account_addresses/api/account_addresses_api_hive.dart @@ -5,16 +5,6 @@ import 'package:komodo_dex/packages/account_addresses/api/account_addresses_api_ import 'package:komodo_dex/packages/account_addresses/models/wallet_address.dart'; import 'package:meta/meta.dart'; -class HiveException implements Exception { - final String message; - final Exception originalException; - - HiveException(this.message, [this.originalException]); - - @override - String toString() => 'HiveException: $message'; -} - class AccountAddressesApiHive implements AccountAddressesApiInterface { final Box _box; @@ -32,7 +22,7 @@ class AccountAddressesApiHive implements AccountAddressesApiInterface { try { box = await Hive.openBox('account_addresses'); } catch (e) { - throw HiveException('Failed to open Hive box: $e', e); + throw Exception('Failed to open Hive box: $e'); } return AccountAddressesApiHive._(box); } @@ -62,7 +52,7 @@ class AccountAddressesApiHive implements AccountAddressesApiInterface { ), ); } catch (e) { - throw HiveException('Failed to create WalletAddress: $e', e); + throw Exception('Failed to create WalletAddress: $e'); } } @@ -122,7 +112,7 @@ class AccountAddressesApiHive implements AccountAddressesApiInterface { try { await _box.put(key, updatedWalletAddress); } catch (e) { - throw HiveException('Failed to update WalletAddress: $e', e); + throw Exception('Failed to update WalletAddress: $e'); } } @@ -134,7 +124,7 @@ class AccountAddressesApiHive implements AccountAddressesApiInterface { try { await _box.delete(_getKey(walletId, address)); } catch (e) { - throw HiveException('Failed to delete WalletAddress: $e', e); + throw Exception('Failed to delete WalletAddress: $e'); } } @@ -144,7 +134,7 @@ class AccountAddressesApiHive implements AccountAddressesApiInterface { try { await _box.deleteAll(keys); } catch (e) { - throw HiveException('Failed to delete WalletAddresses: $e', e); + throw Exception('Failed to delete WalletAddresses: $e'); } } @@ -156,7 +146,7 @@ class AccountAddressesApiHive implements AccountAddressesApiInterface { try { return _box.get(_getKey(walletId, address)); } catch (e) { - throw HiveException('Failed to read WalletAddress: $e', e); + throw Exception('Failed to read WalletAddress: $e'); } } @@ -167,7 +157,7 @@ class AccountAddressesApiHive implements AccountAddressesApiInterface { .where((walletAddress) => walletAddress.walletId == walletId) .toList(); } catch (e) { - throw HiveException('Failed to read WalletAddresses: $e', e); + throw Exception('Failed to read WalletAddresses: $e'); } } @@ -186,7 +176,7 @@ class AccountAddressesApiHive implements AccountAddressesApiInterface { try { await _box.close(); } catch (e) { - throw HiveException('Failed to close Hive box: $e', e); + throw Exception('Failed to close Hive box: $e'); } } From fac2231069b45425d023b3d708af1800ec99fd66 Mon Sep 17 00:00:00 2001 From: CharlVS <77973576+CharlVS@users.noreply.github.com> Date: Mon, 3 Jul 2023 20:51:50 +0200 Subject: [PATCH 20/24] Add a real-time stream for active wallet changes --- lib/model/wallet.dart | 6 ++++++ lib/services/db/database.dart | 30 +++++++++++++++++++++++++++++- 2 files changed, 35 insertions(+), 1 deletion(-) diff --git a/lib/model/wallet.dart b/lib/model/wallet.dart index d90b96f99..a3f1192d0 100644 --- a/lib/model/wallet.dart +++ b/lib/model/wallet.dart @@ -27,4 +27,10 @@ class Wallet { 'id': id ?? '', 'name': name ?? '', }; + + static bool areWalletsEqual(Wallet wallet1, Wallet wallet2) { + assert(wallet1 != null && wallet2 != null, "Can't compare null wallets"); + + return wallet1.id == wallet2.id && wallet1.name == wallet2.name; + } } diff --git a/lib/services/db/database.dart b/lib/services/db/database.dart index 8a77ee034..64b0cd477 100644 --- a/lib/services/db/database.dart +++ b/lib/services/db/database.dart @@ -2,6 +2,8 @@ import 'dart:async'; import 'dart:convert'; import 'dart:io'; +import 'package:async/async.dart'; + import '../../blocs/wallet_bloc.dart'; import '../../model/article.dart'; import '../../model/coin.dart'; @@ -656,6 +658,32 @@ class Db { whereArgs: allWallets ? null : [currentWallet.id], ); - batch.commit(); + await batch.commit(); + + if (_activeWalletController.hasListener) { + _activeWalletController.add(currentWallet); + } + } + + // ===== Wallet watcher logic methods + static StreamController _activeWalletController = + StreamController.broadcast(); + + static Stream watchCurrentWallet() async* { + final Database db = await Db.db; + + Wallet _lastStreamWallet = await getCurrentWallet(); + yield _lastStreamWallet; + + await for (final walletUpdate in _activeWalletController.stream) + if (!Wallet.areWalletsEqual(_lastStreamWallet, walletUpdate)) { + _lastStreamWallet = walletUpdate; + + Log( + 'database:watchCurrentWallet', + 'Wallet updated, yielding new wallet of ID = ${walletUpdate.id}', + ); + yield walletUpdate; + } } } From e1e8ba2f2e6c424650f80d7a563ea63f08629c43 Mon Sep 17 00:00:00 2001 From: CharlVS <77973576+CharlVS@users.noreply.github.com> Date: Mon, 3 Jul 2023 21:19:50 +0200 Subject: [PATCH 21/24] Add stream for wallet snapshot changes --- lib/services/db/database.dart | 35 ++++++++++++++++++++++++++++++++--- 1 file changed, 32 insertions(+), 3 deletions(-) diff --git a/lib/services/db/database.dart b/lib/services/db/database.dart index 64b0cd477..4b1141ede 100644 --- a/lib/services/db/database.dart +++ b/lib/services/db/database.dart @@ -171,7 +171,12 @@ class Db { // even though they aren't in the db due to the ListOfCoinsActivated migration batch.execute('DELETE FROM WalletSnapshot'); - batch.commit(); + await batch.commit(); + + if (_walletSnapshotController.hasListener) { + // Wallet snapshots have been cleared + _walletSnapshotController.add(null); + } Log('database', 'initDB, onUpgrade, upgraded database to version $newVersion successfully'); } catch (e) { @@ -540,7 +545,18 @@ class Db { await db.insert('WalletSnapshot', {'wallet_id': wallet.id, 'snapshot': jsonStr}, conflictAlgorithm: ConflictAlgorithm.replace); - } catch (_) {} + + if (_walletSnapshotController.hasListener) { + _walletSnapshotController.add( + {'wallet_id': wallet.id, 'snapshot': jsonDecode(jsonStr)}, + ); + } + } catch (e) { + Log( + 'database:saveWalletSnapshot', + 'Failed to save wallet snapshot: $e. $jsonStr', + ); + } } static Future getWalletSnapshot() async { @@ -666,9 +682,22 @@ class Db { } // ===== Wallet watcher logic methods - static StreamController _activeWalletController = + static final StreamController _activeWalletController = StreamController.broadcast(); + static final StreamController> + _walletSnapshotController = + StreamController>.broadcast(); + + /// This is a stream that updates when the wallet snapshot table is saved. + /// + /// It is not guaranteed to be the same as the current wallet, and the data + /// may be repeated. + /// + /// The stream does not emit initial data. + static Stream> watchChangesWalletSnapshot() => + _walletSnapshotController.stream; + static Stream watchCurrentWallet() async* { final Database db = await Db.db; From 7197b763e988ee2b44cedc8184b499f2e76037a1 Mon Sep 17 00:00:00 2001 From: CharlVS <77973576+CharlVS@users.noreply.github.com> Date: Tue, 4 Jul 2023 10:54:35 +0200 Subject: [PATCH 22/24] Add wallet address serialisation metods --- .../account_addresses/models/wallet_address.dart | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/lib/packages/account_addresses/models/wallet_address.dart b/lib/packages/account_addresses/models/wallet_address.dart index 75a37d89c..4e5145659 100644 --- a/lib/packages/account_addresses/models/wallet_address.dart +++ b/lib/packages/account_addresses/models/wallet_address.dart @@ -27,4 +27,17 @@ class WalletAddress { @required this.availableBalance, @required this.accountId, }); + + Map toJson() => { + 'walletId': walletId, + 'address': address, + 'ticker': ticker, + 'availableBalance': availableBalance, + 'accountId': accountId, + }; + + @override + String toString() { + return 'WalletAddress{walletId: $walletId, address: $address, ticker: $ticker, availableBalance: $availableBalance, accountId: $accountId}'; + } } From 8631c9bbeb483c7c18e94d7d7abad1e019880b97 Mon Sep 17 00:00:00 2001 From: CharlVS <77973576+CharlVS@users.noreply.github.com> Date: Tue, 4 Jul 2023 10:58:05 +0200 Subject: [PATCH 23/24] Save wallet changes in DB class methods Save wallet changes in DB class methods instead of polling for changes. --- lib/main.dart | 4 + .../account_addresses_repository.dart | 115 +++++++++--------- lib/services/db/database.dart | 33 ++++- lib/services/db/persistence_manager.dart | 19 +++ 4 files changed, 110 insertions(+), 61 deletions(-) create mode 100644 lib/services/db/persistence_manager.dart diff --git a/lib/main.dart b/lib/main.dart index 50812f355..846aef632 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -8,6 +8,7 @@ import 'package:komodo_dex/packages/z_coin_activation/bloc/z_coin_activation_blo import 'package:komodo_dex/packages/z_coin_activation/bloc/z_coin_activation_state.dart'; import 'package:komodo_dex/packages/z_coin_activation/bloc/z_coin_notifications.dart'; import 'package:komodo_dex/packages/z_coin_activation/widgets/z_coin_status_list_tile.dart'; +import 'package:komodo_dex/services/db/persistence_manager.dart'; import '../app_config/app_config.dart'; import '../blocs/authenticate_bloc.dart'; import '../blocs/coins_bloc.dart'; @@ -71,6 +72,9 @@ Future startApp() async { await ZCoinProgressNotifications.initNotifications(); try { mmSe.metrics(); + + await PersistenceManager.init(); + startup.start(); return runApp( diff --git a/lib/packages/account_addresses/repository/account_addresses_repository.dart b/lib/packages/account_addresses/repository/account_addresses_repository.dart index 2e7c99a19..0b977133c 100644 --- a/lib/packages/account_addresses/repository/account_addresses_repository.dart +++ b/lib/packages/account_addresses/repository/account_addresses_repository.dart @@ -1,5 +1,4 @@ import 'dart:async'; -import 'dart:convert'; import 'package:komodo_dex/model/coin_balance.dart'; import 'package:komodo_dex/packages/account_addresses/api/account_addresses_api_interface.dart'; import 'package:komodo_dex/packages/account_addresses/models/wallet_address.dart'; @@ -13,73 +12,73 @@ class AccountAddressesRepository { @required AccountAddressesApiInterface accountAddressesApi, }) : _accountAddressesApi = accountAddressesApi; - Stream watchCurrentWalletAddresses() async* { - StreamController controller; - StreamSubscription subscription; + Future clearAll(String walletId) async { + await _accountAddressesApi.deleteAll(walletId: walletId); + } + + Future storeSnapshot({ + @required List> snapshotListJson, + @required String walletId, + }) async { + if (snapshotListJson == null) return; - controller = StreamController( - onListen: () async { - String currentWalletId; + for (final item in snapshotListJson ?? []) { + final coinBalance = CoinBalance.fromJson(item); - while (controller.hasListener) { - final currentWallet = await Db.getCurrentWallet(); - if (currentWallet != null && currentWallet.id != currentWalletId) { - currentWalletId = currentWallet.id; + final walletAddress = WalletAddress( + walletId: walletId, + address: coinBalance.balance.address, + ticker: coinBalance.coin.abbr, + availableBalance: coinBalance.balance.balance.toDouble(), + accountId: 'iguana', + ); - await subscription?.cancel(); + await _accountAddressesApi.updateOrCreate( + walletId: walletAddress.walletId, + address: walletAddress.address, + ticker: walletAddress.ticker, + availableBalance: walletAddress.availableBalance, + accountId: walletAddress.accountId, + ); + } + } - // Emit the switched wallet information - final String jsonStr = await Db.getWalletSnapshot(); - if (jsonStr != null) { - List items; - try { - items = json.decode(jsonStr); - } catch (e) { - controller - .addError('Failed to decode getWalletSnapshot JSON: $e'); - } + /// Returns a stream of all addresses for the current wallet. + /// + /// The stream emits all initial addresses for the current wallet, and then + /// watches for updated/created addresses for the current wallet. + /// + /// The stream will switch to a new stream when the current wallet changes. + /// + Stream watchCurrentWalletAddresses() async* { + String lastStreamWalletId; - if (items != null) { - for (final item in items) { - final coinBalance = CoinBalance.fromJson(item); + final didWalletChange = (wallet) => + wallet != null && + (wallet.id != lastStreamWalletId || lastStreamWalletId == null); - final walletAddress = WalletAddress( - walletId: currentWalletId, - address: coinBalance.balance.address, - ticker: coinBalance.coin.abbr, - availableBalance: coinBalance.balance.balance.toDouble(), - accountId: 'iguana', - ); + await for (final wallet in Db.watchCurrentWallet().where(didWalletChange)) { + lastStreamWalletId = wallet.id; - await _accountAddressesApi.updateOrCreate( - walletId: walletAddress.walletId, - address: walletAddress.address, - ticker: walletAddress.ticker, - availableBalance: walletAddress.availableBalance, - accountId: walletAddress.accountId, - ); + for (final address in await _accountAddressesApi.readAll( + walletId: lastStreamWalletId, + )) { + yield address; + } - controller.add(walletAddress); - } - } - } + final addressesStream = _accountAddressesApi + .watchAll( + walletId: lastStreamWalletId, + ) + .takeWhile((address) => address.walletId == lastStreamWalletId); - // Listen and emit newer changes that will occur - subscription = _accountAddressesApi - .watchAll(walletId: currentWalletId) - .listen((updatedAddress) { - controller.add(updatedAddress); - }); - } + await for (final address in addressesStream) { + yield address; - await Future.delayed(Duration(seconds: 5)); + if (address.walletId != (await Db.getCurrentWallet())?.id) { + break; } - }, - onCancel: () async { - await subscription?.cancel(); - }, - ); - - yield* controller.stream; + } + } } } diff --git a/lib/services/db/database.dart b/lib/services/db/database.dart index 4b1141ede..16bf7deaa 100644 --- a/lib/services/db/database.dart +++ b/lib/services/db/database.dart @@ -3,6 +3,9 @@ import 'dart:convert'; import 'dart:io'; import 'package:async/async.dart'; +import 'package:flutter/foundation.dart'; +import 'package:komodo_dex/packages/account_addresses/api/account_addresses_api_hive.dart'; +import 'package:komodo_dex/packages/account_addresses/repository/account_addresses_repository.dart'; import '../../blocs/wallet_bloc.dart'; import '../../model/article.dart'; @@ -18,19 +21,32 @@ class Db { static Database _db; static bool _initInvoked = false; + static AccountAddressesRepository _accountAddressesRepository; + static Future get db async { + assert(_initInvoked, 'Db.init must be invoked before accessing the db'); // Protect the database from being opened and initialized multiple times. if (_initInvoked) { await pauseUntil(() => _db != null); return _db; } - _initInvoked = true; _db = await _initDB(); return _db; } + static Future init({ + @required AccountAddressesRepository accountAddressesRepository, + }) async { + _initInvoked = true; + + _accountAddressesRepository = accountAddressesRepository; + _db = await _initDB(); + } + static Future _initDB() async { + if (_db != null) return _db; + final Directory documentsDirectory = await applicationDocumentsDirectory; final String path = join(documentsDirectory.path, 'AtomicDEX.db'); String _articleTable = ''' @@ -546,9 +562,19 @@ class Db { {'wallet_id': wallet.id, 'snapshot': jsonStr}, conflictAlgorithm: ConflictAlgorithm.replace); + final decodedSnapshot = JsonDecoder().convert(jsonStr); + + // convert List to List> + final snapshotListJson = List>.from(decodedSnapshot); + + await _accountAddressesRepository.storeSnapshot( + snapshotListJson: snapshotListJson, + walletId: wallet.id, + ); + if (_walletSnapshotController.hasListener) { _walletSnapshotController.add( - {'wallet_id': wallet.id, 'snapshot': jsonDecode(jsonStr)}, + {'wallet_id': wallet.id, 'snapshot': decodedSnapshot}, ); } } catch (e) { @@ -704,7 +730,8 @@ class Db { Wallet _lastStreamWallet = await getCurrentWallet(); yield _lastStreamWallet; - await for (final walletUpdate in _activeWalletController.stream) + await for (final walletUpdate in _activeWalletController.stream + .where((wallet) => wallet?.id != _lastStreamWallet?.id)) if (!Wallet.areWalletsEqual(_lastStreamWallet, walletUpdate)) { _lastStreamWallet = walletUpdate; diff --git a/lib/services/db/persistence_manager.dart b/lib/services/db/persistence_manager.dart new file mode 100644 index 000000000..0b8a7aa69 --- /dev/null +++ b/lib/services/db/persistence_manager.dart @@ -0,0 +1,19 @@ +import 'package:hive/hive.dart'; +import 'package:komodo_dex/packages/account_addresses/api/account_addresses_api_hive.dart'; +import 'package:komodo_dex/packages/account_addresses/repository/account_addresses_repository.dart'; +import 'package:komodo_dex/services/db/database.dart'; +import 'package:komodo_dex/utils/utils.dart'; + +class PersistenceManager { + PersistenceManager._(); + + static Future init() async { + Hive.init((await applicationDocumentsDirectory).path + '/hive'); + + final accountAddressesRepository = AccountAddressesRepository( + accountAddressesApi: await AccountAddressesApiHive.initialize(), + ); + + await Db.init(accountAddressesRepository: accountAddressesRepository); + } +} From 9d3548ed638ca590a0c98380306f465e866c4086 Mon Sep 17 00:00:00 2001 From: CharlVS <77973576+CharlVS@users.noreply.github.com> Date: Tue, 4 Jul 2023 10:59:47 +0200 Subject: [PATCH 24/24] Add realtime address list helper method for bloc Emit a full list of wallet addresses whenever a change happens. This will be useful in blocs where we want to show a list of all the data. --- .../api/account_addresses_api_hive.dart | 16 ++++++++++++++++ .../api/account_addresses_api_interface.dart | 4 ++++ 2 files changed, 20 insertions(+) diff --git a/lib/packages/account_addresses/api/account_addresses_api_hive.dart b/lib/packages/account_addresses/api/account_addresses_api_hive.dart index 05a30bf1c..292b6ad3a 100644 --- a/lib/packages/account_addresses/api/account_addresses_api_hive.dart +++ b/lib/packages/account_addresses/api/account_addresses_api_hive.dart @@ -172,6 +172,22 @@ class AccountAddressesApiHive implements AccountAddressesApiInterface { .map((event) => event.value as WalletAddress); } + @override + Stream> watchAllList({@required String walletId}) async* { + List addresses = await readAll(walletId: walletId); + yield addresses; + + yield* watchAll(walletId: walletId).map>( + (walletAddress) => addresses = addresses + .map( + (address) => address.address == walletAddress.address + ? walletAddress + : address, + ) + .toList(), + ); + } + Future close() async { try { await _box.close(); diff --git a/lib/packages/account_addresses/api/account_addresses_api_interface.dart b/lib/packages/account_addresses/api/account_addresses_api_interface.dart index 2422ddd94..9a054d2a8 100644 --- a/lib/packages/account_addresses/api/account_addresses_api_interface.dart +++ b/lib/packages/account_addresses/api/account_addresses_api_interface.dart @@ -47,4 +47,8 @@ abstract class AccountAddressesApiInterface { Stream watchAll({ @required String walletId, }); + + Stream> watchAllList({ + @required String walletId, + }); }