Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
06cf892
feat: cherry-pick f880ed83ad55a84d252f8abc796c89be0e38cbca
taylorbell Nov 21, 2024
eb5abf7
fix: ensure state management happens appropriately
dballance Apr 14, 2023
51d1d8f
fix: resolve error on disconnect
dballance Apr 14, 2023
e921366
fix: ensure restored state is included
dballance Apr 14, 2023
048980a
fix: automatically discover services when required on restoration
dballance Apr 17, 2023
9d8453d
feat: expose unsubscribe and disconnect methods
dballance Apr 21, 2023
c992fd4
fix: resolve a CharacteristicInstance in unsubscribeToCharacteristic
taylorbell Nov 22, 2024
899ea1c
feat: add connected devices - cleanup some restoration bits
dballance Apr 27, 2023
fe5b8b1
fix: add getConnectedDevices support on Android
dballance May 2, 2023
6cfed6f
fix: kotlin error reading connected devices
dballance Jun 13, 2023
c2d8402
fix: actually fix kotlin error
dballance Jun 13, 2023
fd1a958
chore: regenerate mocks
taylorbell Nov 22, 2024
cd42627
refactor: emit restored devices via a stream automatically on initialize
taylorbell Nov 26, 2024
38e725e
chore: remove unusued import
taylorbell Nov 26, 2024
1484bee
fix: add event channel handler on Android for restored devices
taylorbell Dec 10, 2024
525b928
fix: ensure restored devices stream completes on Android
taylorbell Dec 14, 2024
0aa6d67
feat: add support for monitoring ble device bond status changes
taylorbell Jan 6, 2025
ae31c30
chore: remove debug logging
taylorbell Jan 6, 2025
5d83f44
fix: ensure duplicate bond updates are not emitted
taylorbell Jan 6, 2025
f9021f2
chore: clean up imports
taylorbell Jan 6, 2025
bd1a18b
fix: report bond state before connection state
taylorbell Jan 7, 2025
607b802
chore: rename connectionState to bondState in BondStateUpdate
taylorbell Jan 7, 2025
665619d
chore: update formatting to appease ktlint
taylorbell Jan 7, 2025
465b2bb
chore: additional formatting fixes
taylorbell Jan 7, 2025
5425f31
Merge pull request #2 from Tango-Tango/feature/bond-status-tracking
taylorbell Jan 7, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion analysis_options.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,10 @@ linter:
# - diagnostic_describe_all_properties -- DISABLED: experimental feature
- empty_statements
- hash_and_equals
- invariant_booleans
# - invariant_booleans
- iterable_contains_unrelated_type
- collection_methods_unrelated_type
- list_remove_unrelated_type
- literal_only_boolean_expressions
- no_adjacent_strings_in_list
- no_duplicate_case_values
Expand Down
4 changes: 2 additions & 2 deletions example/android/app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ apply plugin: 'kotlin-android'
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"

android {
compileSdkVersion 33
compileSdkVersion 34

sourceSets {
main.java.srcDirs += 'src/main/kotlin'
Expand All @@ -45,7 +45,7 @@ android {
defaultConfig {
applicationId "com.signify.hue.reactivebleexample"
minSdkVersion 21
targetSdkVersion 33
targetSdkVersion 34
versionCode flutterVersionCode.toInteger()
versionName flutterVersionName
}
Expand Down
2 changes: 1 addition & 1 deletion example/android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ buildscript {
}

dependencies {
classpath 'com.android.tools.build:gradle:8.0.2'
classpath 'com.android.tools.build:gradle:8.7.3'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}
Expand Down
5 changes: 3 additions & 2 deletions example/android/gradle/wrapper/gradle-wrapper.properties
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#Fri Jun 23 08:50:38 CEST 2017
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.0-all.zip
3 changes: 3 additions & 0 deletions example/devtools_options.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
description: This file stores settings for Dart & Flutter DevTools.
documentation: https://docs.flutter.dev/tools/devtools/extensions#configure-extension-enablement-states
extensions:
2 changes: 1 addition & 1 deletion example/ios/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -30,4 +30,4 @@ SPEC CHECKSUMS:

PODFILE CHECKSUM: c4c93c5f6502fe2754f48404d3594bf779584011

COCOAPODS: 1.14.3
COCOAPODS: 1.15.2
11 changes: 6 additions & 5 deletions example/ios/Runner.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = "<group>"; };
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = "<group>"; };
2DC939A6C6230516B6561DA7 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; };
3AB1B41129E6FF160046DF89 /* Info-Release.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "Info-Release.plist"; sourceTree = "<group>"; };
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; };
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = "<group>"; };
74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
Expand All @@ -44,7 +45,7 @@
97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
97C147021CF9000F007C117D /* Info-Debug.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "Info-Debug.plist"; sourceTree = "<group>"; };
A1AB08F7B02CE763EEA8E83F /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = "<group>"; };
/* End PBXFileReference section */

Expand Down Expand Up @@ -93,10 +94,11 @@
97C146F01CF9000F007C117D /* Runner */ = {
isa = PBXGroup;
children = (
3AB1B41129E6FF160046DF89 /* Info-Release.plist */,
97C146FA1CF9000F007C117D /* Main.storyboard */,
97C146FD1CF9000F007C117D /* Assets.xcassets */,
97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */,
97C147021CF9000F007C117D /* Info.plist */,
97C147021CF9000F007C117D /* Info-Debug.plist */,
97C146F11CF9000F007C117D /* Supporting Files */,
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */,
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */,
Expand Down Expand Up @@ -166,7 +168,6 @@
TargetAttributes = {
97C146ED1CF9000F007C117D = {
CreatedOnToolsVersion = 7.3.1;
DevelopmentTeam = 29DK8LS8B3;
LastSwiftMigration = 0910;
};
};
Expand Down Expand Up @@ -428,7 +429,7 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
DEVELOPMENT_TEAM = 29DK8LS8B3;
DEVELOPMENT_TEAM = 5KK9N97KX3;
ENABLE_BITCODE = NO;
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
Expand All @@ -443,7 +444,7 @@
"$(inherited)",
"$(PROJECT_DIR)/Flutter",
);
PRODUCT_BUNDLE_IDENTIFIER = com.signify.hue.reactivebleExample;
PRODUCT_BUNDLE_IDENTIFIER = com.signify.hue.tangoReactiveBleExample;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
Expand Down
2 changes: 1 addition & 1 deletion example/ios/Runner/AppDelegate.swift
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import UIKit
import Flutter

@UIApplicationMain
@main
@objc class AppDelegate: FlutterAppDelegate {
override func application(
_ application: UIApplication,
Expand Down
10 changes: 9 additions & 1 deletion example/lib/main.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import 'package:flutter/material.dart';
import 'package:flutter_reactive_ble/flutter_reactive_ble.dart';
import 'package:flutter_reactive_ble_example/src/ble/ble_device_bond_monitor.dart';
import 'package:flutter_reactive_ble_example/src/ble/ble_device_connector.dart';
import 'package:flutter_reactive_ble_example/src/ble/ble_device_interactor.dart';
import 'package:flutter_reactive_ble_example/src/ble/ble_scanner.dart';
Expand All @@ -15,12 +16,14 @@ const _themeColor = Colors.lightGreen;
void main() {
WidgetsFlutterBinding.ensureInitialized();

final _ble = FlutterReactiveBle();
final _ble = FlutterReactiveBle.withRestorationKey('someRestorationKey');
final _bleLogger = BleLogger(ble: _ble);
final _scanner = BleScanner(ble: _ble, logMessage: _bleLogger.addToLog);
final _monitor = BleStatusMonitor(_ble);
final _bondMonitor = BleDeviceBondMonitor(_ble);
final _connector = BleDeviceConnector(
ble: _ble,
bondMonitor: _bondMonitor,
logMessage: _bleLogger.addToLog,
);
final _serviceDiscoverer = BleDeviceInteractor(
Expand All @@ -39,6 +42,7 @@ void main() {
Provider.value(value: _connector),
Provider.value(value: _serviceDiscoverer),
Provider.value(value: _bleLogger),
Provider.value(value: _bondMonitor),
StreamProvider<BleScannerState?>(
create: (_) => _scanner.state,
initialData: const BleScannerState(
Expand All @@ -50,6 +54,10 @@ void main() {
create: (_) => _monitor.state,
initialData: BleStatus.unknown,
),
StreamProvider<DeviceBondState>(
create: (_) => _bondMonitor.state,
initialData: DeviceBondState.unknown,
),
StreamProvider<ConnectionStateUpdate>(
create: (_) => _connector.state,
initialData: const ConnectionStateUpdate(
Expand Down
34 changes: 34 additions & 0 deletions example/lib/src/ble/ble_device_bond_monitor.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import 'dart:async';

import 'package:flutter_reactive_ble/flutter_reactive_ble.dart';
import 'package:flutter_reactive_ble_example/src/ble/reactive_state.dart';

class BleDeviceBondMonitor implements ReactiveState<DeviceBondState> {
BleDeviceBondMonitor(this._ble);

final FlutterReactiveBle _ble;

@override
Stream<DeviceBondState> get state => _bondStateController.stream;

final StreamController<DeviceBondState> _bondStateController =
StreamController();

StreamSubscription<DeviceBondState>? _bondStateSubscription;

void startMonitoringDevice(String deviceId) {
_bondStateSubscription ??= _ble.bondUpdateStream
.where((update) => update.deviceId == deviceId)
.map((update) => update.bondState)
.listen(_bondStateController.add);
}

void stopMontoringDevice(String deviceId) {
_bondStateSubscription?.cancel();
_bondStateSubscription = null;
}

Future<void> dispose() async {
await _bondStateController.close();
}
}
8 changes: 8 additions & 0 deletions example/lib/src/ble/ble_device_connector.dart
Original file line number Diff line number Diff line change
@@ -1,16 +1,20 @@
import 'dart:async';

import 'package:flutter_reactive_ble/flutter_reactive_ble.dart';
import 'package:flutter_reactive_ble_example/src/ble/ble_device_bond_monitor.dart';
import 'package:flutter_reactive_ble_example/src/ble/reactive_state.dart';

class BleDeviceConnector extends ReactiveState<ConnectionStateUpdate> {
BleDeviceConnector({
required FlutterReactiveBle ble,
required BleDeviceBondMonitor bondMonitor,
required void Function(String message) logMessage,
}) : _ble = ble,
_bondMonitor = bondMonitor,
_logMessage = logMessage;

final FlutterReactiveBle _ble;
final BleDeviceBondMonitor _bondMonitor;
final void Function(String message) _logMessage;

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

Future<void> connect(String deviceId) async {
_bondMonitor.startMonitoringDevice(deviceId);

_logMessage('Start connecting to $deviceId');
_connection = _ble.connectToDevice(id: deviceId).listen(
(update) {
Expand All @@ -35,6 +41,8 @@ class BleDeviceConnector extends ReactiveState<ConnectionStateUpdate> {
}

Future<void> disconnect(String deviceId) async {
_bondMonitor.stopMontoringDevice(deviceId);

try {
_logMessage('disconnecting to device: $deviceId');
await _connection.cancel();
Expand Down
50 changes: 37 additions & 13 deletions example/lib/src/ui/device_detail/device_interaction_tab.dart
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,19 @@ class DeviceInteractionTab extends StatelessWidget {
final DiscoveredDevice device;

@override
Widget build(BuildContext context) => Consumer3<BleDeviceConnector, ConnectionStateUpdate, BleDeviceInteractor>(
builder: (_, deviceConnector, connectionStateUpdate, serviceDiscoverer, __) => _DeviceInteractionTab(
Widget build(BuildContext context) => Consumer4<BleDeviceConnector,
DeviceBondState, ConnectionStateUpdate, BleDeviceInteractor>(
builder: (_, deviceConnector, bondState, connectionStateUpdate,
serviceDiscoverer, __) =>
_DeviceInteractionTab(
viewModel: DeviceInteractionViewModel(
deviceId: device.id,
bondState: bondState,
connectableStatus: device.connectable,
connectionStatus: connectionStateUpdate.connectionState,
deviceConnector: deviceConnector,
discoverServices: () => serviceDiscoverer.discoverServices(device.id),
discoverServices: () =>
serviceDiscoverer.discoverServices(device.id),
readRssi: () => serviceDiscoverer.readRssi(device.id),
),
),
Expand All @@ -40,6 +45,7 @@ class DeviceInteractionViewModel extends $DeviceInteractionViewModel {
const DeviceInteractionViewModel({
required this.deviceId,
required this.connectableStatus,
required this.bondState,
required this.connectionStatus,
required this.deviceConnector,
required this.discoverServices,
Expand All @@ -48,14 +54,16 @@ class DeviceInteractionViewModel extends $DeviceInteractionViewModel {

final String deviceId;
final Connectable connectableStatus;
final DeviceBondState bondState;
final DeviceConnectionState connectionStatus;
final BleDeviceConnector deviceConnector;
final Future<int> Function() readRssi;

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

bool get deviceConnected => connectionStatus == DeviceConnectionState.connected;
bool get deviceConnected =>
connectionStatus == DeviceConnectionState.connected;

void connect() {
deviceConnector.connect(deviceId);
Expand Down Expand Up @@ -110,7 +118,8 @@ class _DeviceInteractionTabState extends State<_DeviceInteractionTab> {
delegate: SliverChildListDelegate.fixed(
[
Padding(
padding: const EdgeInsetsDirectional.only(top: 8.0, bottom: 16.0, start: 16.0),
padding: const EdgeInsetsDirectional.only(
top: 8.0, bottom: 16.0, start: 16.0),
child: Text(
"ID: ${widget.viewModel.deviceId}",
style: const TextStyle(fontWeight: FontWeight.bold),
Expand All @@ -137,27 +146,39 @@ class _DeviceInteractionTabState extends State<_DeviceInteractionTab> {
style: const TextStyle(fontWeight: FontWeight.bold),
),
),
Padding(
padding: const EdgeInsetsDirectional.only(start: 16.0),
child: Text(
"Bond status: ${widget.viewModel.bondState}",
style: const TextStyle(fontWeight: FontWeight.bold),
),
),
Padding(
padding: const EdgeInsets.only(top: 16.0),
child: Wrap(
alignment: WrapAlignment.spaceEvenly,
children: <Widget>[
ElevatedButton(
onPressed: !widget.viewModel.deviceConnected ? widget.viewModel.connect : null,
onPressed: !widget.viewModel.deviceConnected
? widget.viewModel.connect
: null,
child: const Text("Connect"),
),
ElevatedButton(
onPressed: widget.viewModel.deviceConnected ? widget.viewModel.disconnect : null,
onPressed: widget.viewModel.deviceConnected
? widget.viewModel.disconnect
: null,
child: const Text("Disconnect"),
),
ElevatedButton(
onPressed: widget.viewModel.deviceConnected ? discoverServices : null,
onPressed: widget.viewModel.deviceConnected
? discoverServices
: null,
child: const Text("Discover Services"),
),
ElevatedButton(
onPressed: widget.viewModel.deviceConnected
? readRssi
: null,
onPressed:
widget.viewModel.deviceConnected ? readRssi : null,
child: const Text("Get RSSI"),
),
],
Expand Down Expand Up @@ -222,7 +243,8 @@ class _ServiceDiscoveryListState extends State<_ServiceDiscoveryList> {
Widget _characteristicTile(Characteristic characteristic) => ListTile(
onTap: () => showDialog<void>(
context: context,
builder: (context) => CharacteristicInteractionDialog(characteristic: characteristic),
builder: (context) =>
CharacteristicInteractionDialog(characteristic: characteristic),
),
title: Text(
'${characteristic.id}\n(${_characteristicSummary(characteristic)})',
Expand Down Expand Up @@ -253,7 +275,9 @@ class _ServiceDiscoveryListState extends State<_ServiceDiscoveryList> {
),
Column(
mainAxisSize: MainAxisSize.min,
children: service.characteristics.map(_characteristicTile).toList(),
children: service.characteristics
.map(_characteristicTile)
.toList(),
),
],
),
Expand Down
Loading
Loading