Add Liquid SDK Event & Log stream handlers to example app (#305)

Update example app
* Use defaultConfig on ConnectRequest
* Update feesSat as non-null
* Use payment & walletInfo streams from BreezLiquidSDK
This commit is contained in:
Erdem Yerebasmaz
2024-06-13 14:51:59 +03:00
committed by GitHub
parent bcf84fe8cd
commit 4ed72e70f0
9 changed files with 195 additions and 47 deletions

View File

@@ -9,7 +9,7 @@ void main() {
group('main', () { group('main', () {
setUpAll(() async { setUpAll(() async {
await initApi(); await initApi();
ConnectRequest connectRequest = ConnectRequest(mnemonic: "", network: Network.liquidTestnet); ConnectRequest connectRequest = ConnectRequest(mnemonic: "", config: defaultConfig(network: Network.testnet));
sdk = await connect(req: connectRequest); sdk = await connect(req: connectRequest);
}); });

View File

@@ -5,21 +5,23 @@ import 'package:flutter_breez_liquid_example/routes/home/home_page.dart';
import 'package:flutter_breez_liquid_example/services/credentials_manager.dart'; import 'package:flutter_breez_liquid_example/services/credentials_manager.dart';
import 'package:flutter_breez_liquid_example/services/keychain.dart'; import 'package:flutter_breez_liquid_example/services/keychain.dart';
import 'package:flutter_breez_liquid_example/utils/config.dart'; import 'package:flutter_breez_liquid_example/utils/config.dart';
import 'package:path_provider/path_provider.dart';
import 'services/breez_liquid_sdk.dart';
void main() async { void main() async {
WidgetsFlutterBinding.ensureInitialized(); WidgetsFlutterBinding.ensureInitialized();
await initialize(); await initialize();
final BreezLiquidSDK liquidSDK = BreezLiquidSDK();
final credentialsManager = CredentialsManager(keyChain: KeyChain()); final credentialsManager = CredentialsManager(keyChain: KeyChain());
final mnemonic = await credentialsManager.restoreMnemonic(); final mnemonic = await credentialsManager.restoreMnemonic();
BindingLiquidSdk? liquidSDK;
if (mnemonic.isNotEmpty) { if (mnemonic.isNotEmpty) {
liquidSDK = await reconnect(mnemonic: mnemonic); reconnect(liquidSDK: liquidSDK, mnemonic: mnemonic);
} }
runApp(App(credentialsManager: credentialsManager, liquidSDK: liquidSDK)); runApp(App(credentialsManager: credentialsManager, liquidSDK: liquidSDK));
} }
Future<BindingLiquidSdk> reconnect({ Future<BindingLiquidSdk> reconnect({
required BreezLiquidSDK liquidSDK,
required String mnemonic, required String mnemonic,
Network network = Network.mainnet, Network network = Network.mainnet,
}) async { }) async {
@@ -28,13 +30,13 @@ Future<BindingLiquidSdk> reconnect({
config: config, config: config,
mnemonic: mnemonic, mnemonic: mnemonic,
); );
return await connect(req: req); return await liquidSDK.connect(req: req);
} }
class App extends StatefulWidget { class App extends StatefulWidget {
final CredentialsManager credentialsManager; final CredentialsManager credentialsManager;
final BindingLiquidSdk? liquidSDK; final BreezLiquidSDK liquidSDK;
const App({super.key, required this.credentialsManager, this.liquidSDK}); const App({super.key, required this.credentialsManager, required this.liquidSDK});
static const title = 'Breez Liquid SDK Demo'; static const title = 'Breez Liquid SDK Demo';
@@ -48,9 +50,15 @@ class _AppState extends State<App> {
return MaterialApp( return MaterialApp(
title: App.title, title: App.title,
theme: ThemeData.from(colorScheme: ColorScheme.fromSeed(seedColor: Colors.white), useMaterial3: true), theme: ThemeData.from(colorScheme: ColorScheme.fromSeed(seedColor: Colors.white), useMaterial3: true),
home: widget.liquidSDK == null home: widget.liquidSDK.wallet == null
? ConnectPage(credentialsManager: widget.credentialsManager) ? ConnectPage(
: HomePage(credentialsManager: widget.credentialsManager, liquidSDK: widget.liquidSDK!), liquidSDK: widget.liquidSDK,
credentialsManager: widget.credentialsManager,
)
: HomePage(
credentialsManager: widget.credentialsManager,
liquidSDK: widget.liquidSDK,
),
); );
} }
} }

View File

@@ -3,13 +3,14 @@ import 'package:flutter/material.dart';
import 'package:flutter_breez_liquid/flutter_breez_liquid.dart'; import 'package:flutter_breez_liquid/flutter_breez_liquid.dart';
import 'package:flutter_breez_liquid_example/routes/connect/restore_page.dart'; import 'package:flutter_breez_liquid_example/routes/connect/restore_page.dart';
import 'package:flutter_breez_liquid_example/routes/home/home_page.dart'; import 'package:flutter_breez_liquid_example/routes/home/home_page.dart';
import 'package:flutter_breez_liquid_example/services/breez_liquid_sdk.dart';
import 'package:flutter_breez_liquid_example/services/credentials_manager.dart'; import 'package:flutter_breez_liquid_example/services/credentials_manager.dart';
import 'package:flutter_breez_liquid_example/utils/config.dart'; import 'package:flutter_breez_liquid_example/utils/config.dart';
import 'package:path_provider/path_provider.dart';
class ConnectPage extends StatefulWidget { class ConnectPage extends StatefulWidget {
final BreezLiquidSDK liquidSDK;
final CredentialsManager credentialsManager; final CredentialsManager credentialsManager;
const ConnectPage({super.key, required this.credentialsManager}); const ConnectPage({super.key, required this.liquidSDK, required this.credentialsManager});
@override @override
State<ConnectPage> createState() => _ConnectPageState(); State<ConnectPage> createState() => _ConnectPageState();
@@ -84,7 +85,7 @@ class _ConnectPageState extends State<ConnectPage> {
context, context,
MaterialPageRoute( MaterialPageRoute(
builder: (BuildContext context) => HomePage( builder: (BuildContext context) => HomePage(
liquidSDK: liquidSDK, liquidSDK: widget.liquidSDK,
credentialsManager: widget.credentialsManager, credentialsManager: widget.credentialsManager,
), ),
), ),
@@ -103,6 +104,6 @@ class _ConnectPageState extends State<ConnectPage> {
config: config, config: config,
mnemonic: mnemonic, mnemonic: mnemonic,
); );
return await connect(req: req); return await widget.liquidSDK.connect(req: req);
} }
} }

View File

@@ -1,17 +1,17 @@
import 'dart:async'; import 'dart:async';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_breez_liquid/flutter_breez_liquid.dart';
import 'package:flutter_breez_liquid_example/routes/home/widgets/balance.dart'; import 'package:flutter_breez_liquid_example/routes/home/widgets/balance.dart';
import 'package:flutter_breez_liquid_example/routes/home/widgets/bottom_app_bar.dart'; import 'package:flutter_breez_liquid_example/routes/home/widgets/bottom_app_bar.dart';
import 'package:flutter_breez_liquid_example/routes/home/widgets/drawer.dart'; import 'package:flutter_breez_liquid_example/routes/home/widgets/drawer.dart';
import 'package:flutter_breez_liquid_example/routes/home/widgets/payment_list/payment_list.dart'; import 'package:flutter_breez_liquid_example/routes/home/widgets/payment_list/payment_list.dart';
import 'package:flutter_breez_liquid_example/routes/home/widgets/qr_scan_action_button.dart'; import 'package:flutter_breez_liquid_example/routes/home/widgets/qr_scan_action_button.dart';
import 'package:flutter_breez_liquid_example/services/breez_liquid_sdk.dart';
import 'package:flutter_breez_liquid_example/services/credentials_manager.dart'; import 'package:flutter_breez_liquid_example/services/credentials_manager.dart';
class HomePage extends StatefulWidget { class HomePage extends StatefulWidget {
final CredentialsManager credentialsManager; final CredentialsManager credentialsManager;
final BindingLiquidSdk liquidSDK; final BreezLiquidSDK liquidSDK;
const HomePage({super.key, required this.credentialsManager, required this.liquidSDK}); const HomePage({super.key, required this.credentialsManager, required this.liquidSDK});
@@ -20,26 +20,6 @@ class HomePage extends StatefulWidget {
} }
class _HomePageState extends State<HomePage> { class _HomePageState extends State<HomePage> {
Stream<GetInfoResponse> walletInfoStream() async* {
debugPrint("Initialized walletInfoStream");
yield await widget.liquidSDK.getInfo();
while (true) {
await Future.delayed(const Duration(seconds: 10));
yield await widget.liquidSDK.getInfo();
debugPrint("Refreshed wallet info");
}
}
Stream<List<Payment>> paymentsStream() async* {
debugPrint("Initialized paymentsStream");
yield await widget.liquidSDK.listPayments();
while (true) {
await Future.delayed(const Duration(seconds: 10));
yield await widget.liquidSDK.listPayments();
debugPrint("Refreshed payments");
}
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return MaterialApp( return MaterialApp(
@@ -70,13 +50,13 @@ class _HomePageState extends State<HomePage> {
Container( Container(
height: constraints.maxHeight * 0.3, height: constraints.maxHeight * 0.3,
color: Colors.white, color: Colors.white,
child: Balance(walletInfoStream: walletInfoStream().asBroadcastStream()), child: Balance(walletInfoStream: widget.liquidSDK.walletInfoStream),
), ),
Container( Container(
height: constraints.maxHeight * 0.7, height: constraints.maxHeight * 0.7,
color: Colors.white, color: Colors.white,
child: PaymentList( child: PaymentList(
paymentsStream: paymentsStream().asBroadcastStream(), paymentsStream: widget.liquidSDK.paymentsStream,
onRefresh: () async => await _sync(), onRefresh: () async => await _sync(),
), ),
), ),
@@ -84,12 +64,13 @@ class _HomePageState extends State<HomePage> {
); );
}, },
), ),
drawer: HomePageDrawer(liquidSDK: widget.liquidSDK, credentialsManager: widget.credentialsManager), drawer: HomePageDrawer(
floatingActionButton: QrActionButton(liquidSDK: widget.liquidSDK), liquidSDK: widget.liquidSDK.wallet!, credentialsManager: widget.credentialsManager),
floatingActionButton: QrActionButton(liquidSDK: widget.liquidSDK.wallet!),
floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked, floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked,
bottomNavigationBar: HomePageBottomAppBar( bottomNavigationBar: HomePageBottomAppBar(
liquidSDK: widget.liquidSDK, liquidSDK: widget.liquidSDK.wallet!,
paymentsStream: paymentsStream().asBroadcastStream(), paymentsStream: widget.liquidSDK.paymentsStream,
), ),
), ),
); );
@@ -98,7 +79,7 @@ class _HomePageState extends State<HomePage> {
Future<void> _sync() async { Future<void> _sync() async {
try { try {
debugPrint("Syncing wallet."); debugPrint("Syncing wallet.");
await widget.liquidSDK.sync(); await widget.liquidSDK.wallet!.sync();
debugPrint("Wallet synced!"); debugPrint("Wallet synced!");
} on Exception catch (e) { } on Exception catch (e) {
final errMsg = "Failed to sync wallet. $e"; final errMsg = "Failed to sync wallet. $e";

View File

@@ -49,7 +49,7 @@ class HomePageBottomAppBar extends StatelessWidget {
context: context, context: context,
builder: (context) => ReceivePaymentDialog( builder: (context) => ReceivePaymentDialog(
liquidSDK: liquidSDK, liquidSDK: liquidSDK,
paymentsStream: paymentsStream.asBroadcastStream(), paymentsStream: paymentsStream,
), ),
); );
}, },

View File

@@ -62,7 +62,7 @@ class PaymentItem extends StatelessWidget {
"${item.paymentType == PaymentType.send ? "-" : "+"}${item.amountSat} sats", "${item.paymentType == PaymentType.send ? "-" : "+"}${item.amountSat} sats",
style: Theme.of(context).textTheme.bodyLarge, style: Theme.of(context).textTheme.bodyLarge,
), ),
if (item.feesSat != null) ...[ if (item.feesSat != BigInt.zero) ...[
Text("FEE: ${item.paymentType == PaymentType.receive ? "-" : ""}${item.feesSat} sats"), Text("FEE: ${item.paymentType == PaymentType.receive ? "-" : ""}${item.feesSat} sats"),
] ]
], ],

View File

@@ -0,0 +1,149 @@
import 'dart:async';
import 'package:flutter_breez_liquid/flutter_breez_liquid.dart' as liquid_sdk;
import 'package:rxdart/rxdart.dart';
class BreezLiquidSDK {
liquid_sdk.BindingLiquidSdk? wallet;
Future<liquid_sdk.BindingLiquidSdk> connect({
required liquid_sdk.ConnectRequest req,
}) async {
wallet = await liquid_sdk.connect(req: req);
_initializeEventsStream(wallet!);
_subscribeToSdkStreams(wallet!);
await _fetchWalletData(wallet!);
return wallet!;
}
void disconnect(liquid_sdk.BindingLiquidSdk sdk) {
sdk.disconnect();
_unsubscribeFromSdkStreams();
}
Future _fetchWalletData(liquid_sdk.BindingLiquidSdk sdk) async {
await _getInfo(sdk);
await _listPayments(sdk);
}
Future<liquid_sdk.GetInfoResponse> _getInfo(liquid_sdk.BindingLiquidSdk sdk) async {
final walletInfo = await sdk.getInfo(req: const liquid_sdk.GetInfoRequest(withScan: false));
_walletInfoController.add(walletInfo);
return walletInfo;
}
Future<List<liquid_sdk.Payment>> _listPayments(liquid_sdk.BindingLiquidSdk sdk) async {
final paymentsList = await sdk.listPayments();
_paymentsController.add(paymentsList);
return paymentsList;
}
StreamSubscription<liquid_sdk.LogEntry>? _breezLogSubscription;
Stream<liquid_sdk.LogEntry>? _breezLogStream;
/// Initializes SDK log stream.
///
/// Call once on your Dart entrypoint file, e.g.; `lib/main.dart`.
void initializeLogStream() {
_breezLogStream ??= liquid_sdk.breezLogStream().asBroadcastStream();
}
StreamSubscription<liquid_sdk.LiquidSdkEvent>? _breezEventsSubscription;
Stream<liquid_sdk.LiquidSdkEvent>? _breezEventsStream;
void _initializeEventsStream(liquid_sdk.BindingLiquidSdk sdk) {
_breezEventsStream ??= sdk.addEventListener().asBroadcastStream();
}
/// Subscribes to SDK's event & log streams.
void _subscribeToSdkStreams(liquid_sdk.BindingLiquidSdk sdk) {
_subscribeToEventsStream(sdk);
_subscribeToLogStream();
}
final StreamController<liquid_sdk.GetInfoResponse> _walletInfoController =
BehaviorSubject<liquid_sdk.GetInfoResponse>();
Stream<liquid_sdk.GetInfoResponse> get walletInfoStream => _walletInfoController.stream;
final StreamController<liquid_sdk.Payment> _paymentResultStream = StreamController.broadcast();
final StreamController<List<liquid_sdk.Payment>> _paymentsController =
BehaviorSubject<List<liquid_sdk.Payment>>();
Stream<List<liquid_sdk.Payment>> get paymentsStream => _paymentsController.stream;
Stream<liquid_sdk.Payment> get paymentResultStream => _paymentResultStream.stream;
/* TODO: Liquid - Log statements are added for debugging purposes, should be removed after early development stage is complete & events are behaving as expected.*/
/// Subscribes to LiquidSdkEvent's stream
void _subscribeToEventsStream(liquid_sdk.BindingLiquidSdk sdk) {
_breezEventsSubscription = _breezEventsStream?.listen(
(event) async {
if (event is liquid_sdk.LiquidSdkEvent_PaymentFailed) {
_logStreamController
.add(liquid_sdk.LogEntry(line: "Payment Failed. ${event.details.swapId}", level: "WARN"));
_paymentResultStream.addError(PaymentException(event.details));
}
if (event is liquid_sdk.LiquidSdkEvent_PaymentPending) {
_logStreamController
.add(liquid_sdk.LogEntry(line: "Payment Pending. ${event.details.swapId}", level: "INFO"));
_paymentResultStream.add(event.details);
}
if (event is liquid_sdk.LiquidSdkEvent_PaymentRefunded) {
_logStreamController
.add(liquid_sdk.LogEntry(line: "Payment Refunded. ${event.details.swapId}", level: "INFO"));
_paymentResultStream.add(event.details);
}
if (event is liquid_sdk.LiquidSdkEvent_PaymentRefundPending) {
_logStreamController.add(
liquid_sdk.LogEntry(line: "Pending Payment Refund. ${event.details.swapId}", level: "INFO"));
_paymentResultStream.add(event.details);
}
if (event is liquid_sdk.LiquidSdkEvent_PaymentSucceeded) {
_logStreamController
.add(liquid_sdk.LogEntry(line: "Payment Succeeded. ${event.details.swapId}", level: "INFO"));
_paymentResultStream.add(event.details);
await _fetchWalletData(sdk);
}
if (event is liquid_sdk.LiquidSdkEvent_PaymentWaitingConfirmation) {
_logStreamController.add(liquid_sdk.LogEntry(
line: "Payment Waiting Confirmation. ${event.details.swapId}", level: "INFO"));
_paymentResultStream.add(event.details);
}
if (event is liquid_sdk.LiquidSdkEvent_Synced) {
_logStreamController.add(const liquid_sdk.LogEntry(line: "Received Synced event.", level: "INFO"));
await _fetchWalletData(sdk);
}
},
);
}
final _logStreamController = StreamController<liquid_sdk.LogEntry>.broadcast();
Stream<liquid_sdk.LogEntry> get logStream => _logStreamController.stream;
/// Subscribes to SDK's logs stream
void _subscribeToLogStream() {
_breezLogSubscription = _breezLogStream?.listen((logEntry) {
_logStreamController.add(logEntry);
}, onError: (e) {
_logStreamController.addError(e);
});
}
/// Unsubscribes from SDK's event & log streams.
void _unsubscribeFromSdkStreams() {
_breezEventsSubscription?.cancel();
_breezLogSubscription?.cancel();
}
}
// TODO: Liquid - Return this exception from the SDK directly
class PaymentException {
final liquid_sdk.Payment details;
const PaymentException(this.details);
}

View File

@@ -402,10 +402,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: platform name: platform
sha256: "12220bb4b65720483f8fa9450b4332347737cf8213dd2840d8b2c823e47243ec" sha256: "9b71283fc13df574056616011fb138fd3b793ea47cc509c189a6c3fa5f8a1a65"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.1.4" version: "3.1.5"
plugin_platform_interface: plugin_platform_interface:
dependency: transitive dependency: transitive
description: description:
@@ -446,6 +446,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.2.1" version: "3.2.1"
rxdart:
dependency: "direct main"
description:
name: rxdart
sha256: "0c7c0cedd93788d996e33041ffecda924cc54389199cde4e6a34b440f50044cb"
url: "https://pub.dev"
source: hosted
version: "0.27.7"
sky_engine: sky_engine:
dependency: transitive dependency: transitive
description: flutter description: flutter

View File

@@ -25,6 +25,7 @@ dependencies:
path_provider: ^2.1.3 path_provider: ^2.1.3
qr_flutter: ^4.1.0 qr_flutter: ^4.1.0
intl: ^0.19.0 intl: ^0.19.0
rxdart: ^0.27.7
dev_dependencies: dev_dependencies:
flutter_test: flutter_test: