Skip to content

Commit 5425f31

Browse files
authored
Merge pull request #2 from Tango-Tango/feature/bond-status-tracking
feat: add support for monitoring ble device bond status changes
2 parents 525b928 + 465b2bb commit 5425f31

40 files changed

+926
-50
lines changed

example/android/app/build.gradle

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ apply plugin: 'kotlin-android'
2626
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
2727

2828
android {
29-
compileSdkVersion 33
29+
compileSdkVersion 34
3030

3131
sourceSets {
3232
main.java.srcDirs += 'src/main/kotlin'
@@ -45,7 +45,7 @@ android {
4545
defaultConfig {
4646
applicationId "com.signify.hue.reactivebleexample"
4747
minSdkVersion 21
48-
targetSdkVersion 33
48+
targetSdkVersion 34
4949
versionCode flutterVersionCode.toInteger()
5050
versionName flutterVersionName
5151
}

example/android/build.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ buildscript {
66
}
77

88
dependencies {
9-
classpath 'com.android.tools.build:gradle:8.0.2'
9+
classpath 'com.android.tools.build:gradle:8.7.3'
1010
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
1111
}
1212
}

example/android/gradle/wrapper/gradle-wrapper.properties

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1-
#Fri Jun 23 08:50:38 CEST 2017
21
distributionBase=GRADLE_USER_HOME
32
distributionPath=wrapper/dists
3+
distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip
4+
networkTimeout=10000
5+
validateDistributionUrl=true
46
zipStoreBase=GRADLE_USER_HOME
57
zipStorePath=wrapper/dists
6-
distributionUrl=https\://services.gradle.org/distributions/gradle-8.0-all.zip

example/devtools_options.yaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
description: This file stores settings for Dart & Flutter DevTools.
2+
documentation: https://docs.flutter.dev/tools/devtools/extensions#configure-extension-enablement-states
3+
extensions:

example/ios/Runner.xcodeproj/project.pbxproj

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
classes = {
55
};
66
objectVersion = 54;
7-
objectVersion = 54;
87
objects = {
98

109
/* Begin PBXBuildFile section */
@@ -169,7 +168,6 @@
169168
TargetAttributes = {
170169
97C146ED1CF9000F007C117D = {
171170
CreatedOnToolsVersion = 7.3.1;
172-
DevelopmentTeam = 29DK8LS8B3;
173171
LastSwiftMigration = 0910;
174172
};
175173
};
@@ -431,7 +429,7 @@
431429
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
432430
CLANG_ENABLE_MODULES = YES;
433431
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
434-
DEVELOPMENT_TEAM = 29DK8LS8B3;
432+
DEVELOPMENT_TEAM = 5KK9N97KX3;
435433
ENABLE_BITCODE = NO;
436434
FRAMEWORK_SEARCH_PATHS = (
437435
"$(inherited)",
@@ -446,7 +444,7 @@
446444
"$(inherited)",
447445
"$(PROJECT_DIR)/Flutter",
448446
);
449-
PRODUCT_BUNDLE_IDENTIFIER = com.signify.hue.reactivebleExample;
447+
PRODUCT_BUNDLE_IDENTIFIER = com.signify.hue.tangoReactiveBleExample;
450448
PRODUCT_NAME = "$(TARGET_NAME)";
451449
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
452450
SWIFT_OPTIMIZATION_LEVEL = "-Onone";

example/lib/main.dart

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import 'package:flutter/material.dart';
22
import 'package:flutter_reactive_ble/flutter_reactive_ble.dart';
3+
import 'package:flutter_reactive_ble_example/src/ble/ble_device_bond_monitor.dart';
34
import 'package:flutter_reactive_ble_example/src/ble/ble_device_connector.dart';
45
import 'package:flutter_reactive_ble_example/src/ble/ble_device_interactor.dart';
56
import 'package:flutter_reactive_ble_example/src/ble/ble_scanner.dart';
@@ -19,8 +20,10 @@ void main() {
1920
final _bleLogger = BleLogger(ble: _ble);
2021
final _scanner = BleScanner(ble: _ble, logMessage: _bleLogger.addToLog);
2122
final _monitor = BleStatusMonitor(_ble);
23+
final _bondMonitor = BleDeviceBondMonitor(_ble);
2224
final _connector = BleDeviceConnector(
2325
ble: _ble,
26+
bondMonitor: _bondMonitor,
2427
logMessage: _bleLogger.addToLog,
2528
);
2629
final _serviceDiscoverer = BleDeviceInteractor(
@@ -39,6 +42,7 @@ void main() {
3942
Provider.value(value: _connector),
4043
Provider.value(value: _serviceDiscoverer),
4144
Provider.value(value: _bleLogger),
45+
Provider.value(value: _bondMonitor),
4246
StreamProvider<BleScannerState?>(
4347
create: (_) => _scanner.state,
4448
initialData: const BleScannerState(
@@ -50,6 +54,10 @@ void main() {
5054
create: (_) => _monitor.state,
5155
initialData: BleStatus.unknown,
5256
),
57+
StreamProvider<DeviceBondState>(
58+
create: (_) => _bondMonitor.state,
59+
initialData: DeviceBondState.unknown,
60+
),
5361
StreamProvider<ConnectionStateUpdate>(
5462
create: (_) => _connector.state,
5563
initialData: const ConnectionStateUpdate(
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import 'dart:async';
2+
3+
import 'package:flutter_reactive_ble/flutter_reactive_ble.dart';
4+
import 'package:flutter_reactive_ble_example/src/ble/reactive_state.dart';
5+
6+
class BleDeviceBondMonitor implements ReactiveState<DeviceBondState> {
7+
BleDeviceBondMonitor(this._ble);
8+
9+
final FlutterReactiveBle _ble;
10+
11+
@override
12+
Stream<DeviceBondState> get state => _bondStateController.stream;
13+
14+
final StreamController<DeviceBondState> _bondStateController =
15+
StreamController();
16+
17+
StreamSubscription<DeviceBondState>? _bondStateSubscription;
18+
19+
void startMonitoringDevice(String deviceId) {
20+
_bondStateSubscription ??= _ble.bondUpdateStream
21+
.where((update) => update.deviceId == deviceId)
22+
.map((update) => update.bondState)
23+
.listen(_bondStateController.add);
24+
}
25+
26+
void stopMontoringDevice(String deviceId) {
27+
_bondStateSubscription?.cancel();
28+
_bondStateSubscription = null;
29+
}
30+
31+
Future<void> dispose() async {
32+
await _bondStateController.close();
33+
}
34+
}

example/lib/src/ble/ble_device_connector.dart

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,20 @@
11
import 'dart:async';
22

33
import 'package:flutter_reactive_ble/flutter_reactive_ble.dart';
4+
import 'package:flutter_reactive_ble_example/src/ble/ble_device_bond_monitor.dart';
45
import 'package:flutter_reactive_ble_example/src/ble/reactive_state.dart';
56

67
class BleDeviceConnector extends ReactiveState<ConnectionStateUpdate> {
78
BleDeviceConnector({
89
required FlutterReactiveBle ble,
10+
required BleDeviceBondMonitor bondMonitor,
911
required void Function(String message) logMessage,
1012
}) : _ble = ble,
13+
_bondMonitor = bondMonitor,
1114
_logMessage = logMessage;
1215

1316
final FlutterReactiveBle _ble;
17+
final BleDeviceBondMonitor _bondMonitor;
1418
final void Function(String message) _logMessage;
1519

1620
@override
@@ -22,6 +26,8 @@ class BleDeviceConnector extends ReactiveState<ConnectionStateUpdate> {
2226
late StreamSubscription<ConnectionStateUpdate> _connection;
2327

2428
Future<void> connect(String deviceId) async {
29+
_bondMonitor.startMonitoringDevice(deviceId);
30+
2531
_logMessage('Start connecting to $deviceId');
2632
_connection = _ble.connectToDevice(id: deviceId).listen(
2733
(update) {
@@ -35,6 +41,8 @@ class BleDeviceConnector extends ReactiveState<ConnectionStateUpdate> {
3541
}
3642

3743
Future<void> disconnect(String deviceId) async {
44+
_bondMonitor.stopMontoringDevice(deviceId);
45+
3846
try {
3947
_logMessage('disconnecting to device: $deviceId');
4048
await _connection.cancel();

example/lib/src/ui/device_detail/device_interaction_tab.dart

Lines changed: 37 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -20,14 +20,19 @@ class DeviceInteractionTab extends StatelessWidget {
2020
final DiscoveredDevice device;
2121

2222
@override
23-
Widget build(BuildContext context) => Consumer3<BleDeviceConnector, ConnectionStateUpdate, BleDeviceInteractor>(
24-
builder: (_, deviceConnector, connectionStateUpdate, serviceDiscoverer, __) => _DeviceInteractionTab(
23+
Widget build(BuildContext context) => Consumer4<BleDeviceConnector,
24+
DeviceBondState, ConnectionStateUpdate, BleDeviceInteractor>(
25+
builder: (_, deviceConnector, bondState, connectionStateUpdate,
26+
serviceDiscoverer, __) =>
27+
_DeviceInteractionTab(
2528
viewModel: DeviceInteractionViewModel(
2629
deviceId: device.id,
30+
bondState: bondState,
2731
connectableStatus: device.connectable,
2832
connectionStatus: connectionStateUpdate.connectionState,
2933
deviceConnector: deviceConnector,
30-
discoverServices: () => serviceDiscoverer.discoverServices(device.id),
34+
discoverServices: () =>
35+
serviceDiscoverer.discoverServices(device.id),
3136
readRssi: () => serviceDiscoverer.readRssi(device.id),
3237
),
3338
),
@@ -40,6 +45,7 @@ class DeviceInteractionViewModel extends $DeviceInteractionViewModel {
4045
const DeviceInteractionViewModel({
4146
required this.deviceId,
4247
required this.connectableStatus,
48+
required this.bondState,
4349
required this.connectionStatus,
4450
required this.deviceConnector,
4551
required this.discoverServices,
@@ -48,14 +54,16 @@ class DeviceInteractionViewModel extends $DeviceInteractionViewModel {
4854

4955
final String deviceId;
5056
final Connectable connectableStatus;
57+
final DeviceBondState bondState;
5158
final DeviceConnectionState connectionStatus;
5259
final BleDeviceConnector deviceConnector;
5360
final Future<int> Function() readRssi;
5461

5562
@CustomEquality(Ignore())
5663
final Future<List<Service>> Function() discoverServices;
5764

58-
bool get deviceConnected => connectionStatus == DeviceConnectionState.connected;
65+
bool get deviceConnected =>
66+
connectionStatus == DeviceConnectionState.connected;
5967

6068
void connect() {
6169
deviceConnector.connect(deviceId);
@@ -110,7 +118,8 @@ class _DeviceInteractionTabState extends State<_DeviceInteractionTab> {
110118
delegate: SliverChildListDelegate.fixed(
111119
[
112120
Padding(
113-
padding: const EdgeInsetsDirectional.only(top: 8.0, bottom: 16.0, start: 16.0),
121+
padding: const EdgeInsetsDirectional.only(
122+
top: 8.0, bottom: 16.0, start: 16.0),
114123
child: Text(
115124
"ID: ${widget.viewModel.deviceId}",
116125
style: const TextStyle(fontWeight: FontWeight.bold),
@@ -137,27 +146,39 @@ class _DeviceInteractionTabState extends State<_DeviceInteractionTab> {
137146
style: const TextStyle(fontWeight: FontWeight.bold),
138147
),
139148
),
149+
Padding(
150+
padding: const EdgeInsetsDirectional.only(start: 16.0),
151+
child: Text(
152+
"Bond status: ${widget.viewModel.bondState}",
153+
style: const TextStyle(fontWeight: FontWeight.bold),
154+
),
155+
),
140156
Padding(
141157
padding: const EdgeInsets.only(top: 16.0),
142158
child: Wrap(
143159
alignment: WrapAlignment.spaceEvenly,
144160
children: <Widget>[
145161
ElevatedButton(
146-
onPressed: !widget.viewModel.deviceConnected ? widget.viewModel.connect : null,
162+
onPressed: !widget.viewModel.deviceConnected
163+
? widget.viewModel.connect
164+
: null,
147165
child: const Text("Connect"),
148166
),
149167
ElevatedButton(
150-
onPressed: widget.viewModel.deviceConnected ? widget.viewModel.disconnect : null,
168+
onPressed: widget.viewModel.deviceConnected
169+
? widget.viewModel.disconnect
170+
: null,
151171
child: const Text("Disconnect"),
152172
),
153173
ElevatedButton(
154-
onPressed: widget.viewModel.deviceConnected ? discoverServices : null,
174+
onPressed: widget.viewModel.deviceConnected
175+
? discoverServices
176+
: null,
155177
child: const Text("Discover Services"),
156178
),
157179
ElevatedButton(
158-
onPressed: widget.viewModel.deviceConnected
159-
? readRssi
160-
: null,
180+
onPressed:
181+
widget.viewModel.deviceConnected ? readRssi : null,
161182
child: const Text("Get RSSI"),
162183
),
163184
],
@@ -222,7 +243,8 @@ class _ServiceDiscoveryListState extends State<_ServiceDiscoveryList> {
222243
Widget _characteristicTile(Characteristic characteristic) => ListTile(
223244
onTap: () => showDialog<void>(
224245
context: context,
225-
builder: (context) => CharacteristicInteractionDialog(characteristic: characteristic),
246+
builder: (context) =>
247+
CharacteristicInteractionDialog(characteristic: characteristic),
226248
),
227249
title: Text(
228250
'${characteristic.id}\n(${_characteristicSummary(characteristic)})',
@@ -253,7 +275,9 @@ class _ServiceDiscoveryListState extends State<_ServiceDiscoveryList> {
253275
),
254276
Column(
255277
mainAxisSize: MainAxisSize.min,
256-
children: service.characteristics.map(_characteristicTile).toList(),
278+
children: service.characteristics
279+
.map(_characteristicTile)
280+
.toList(),
257281
),
258282
],
259283
),

0 commit comments

Comments
 (0)