Add auto-connect support (#206)

* Add autoConnect support

* Enhance autoConnect functionality for Bluetooth devices

- Added support for managing a set of auto-connect devices in both Android and iOS implementations.
- Updated connection and disconnection logic to handle auto-reconnect scenarios appropriately.
- Improved cleanup processes to prevent unwanted reconnections when devices are manually disconnected.

* Refactor connection button and add auto-reconnect toggle

- Updated the connection button layout for better UI consistency.
- Introduced an auto-reconnect toggle to manage automatic reconnections when devices become available.
- Enhanced connection and disconnection logic to respect the auto-connect setting.

* Update version to 1.2.0 and add CHANGELOG for new features

- Bumped version to 1.2.0.
- Added CHANGELOG.md detailing new features including support for `autoConnect` and UI updates for automatic reconnection.

* Improve readme

* Refactor variable naming for clarity in Bluetooth connection logic

- Changed variable name from `isAutoConnect` to `shouldAutoConnect` for better readability and understanding of its purpose in the connection state handling.

* Refactor connection handling in UniversalBlePlugin

- Simplified connection logic by always cleaning up internal state before sending connection change callbacks.
- Ensured GATT resources are only closed when autoConnect is disabled, allowing for better management of Bluetooth connections.

* Refactor disconnection handling in UniversalBlePlugin

- Introduced a new method `handlePeripheralDisconnection` to encapsulate disconnection logic, improving code readability and maintainability.
- Updated the `centralManager` methods to utilize the new disconnection handler, ensuring consistent behavior across connection state changes.

* Update darwin/Classes/UniversalBlePlugin.swift

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

---------

Co-authored-by: Foti Dim <fotios.dimanidis@a2zebra.de>
Co-authored-by: Navideck Labs <130186950+navidecklabs@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
Foti Dim
2026-01-16 07:06:24 +01:00
committed by GitHub
parent a30e84a70c
commit 8557a46d05
28 changed files with 323 additions and 106 deletions
+8
View File
@@ -0,0 +1,8 @@
## 1.1.0
* Add support for `autoConnect` parameter
* Display RSSI values in device details
* Persist filters
* Fix clear log button
## 1.0.0
* Initial release
+1 -1
View File
@@ -52,7 +52,7 @@ class MockUniversalBle extends UniversalBlePlatform {
}
@override
Future<void> connect(String deviceId, {Duration? connectionTimeout}) async {
Future<void> connect(String deviceId, {bool autoConnect = false, Duration? connectionTimeout}) async {
updateConnection(deviceId, true);
_connectionStateMap[deviceId] = BleConnectionState.connected;
}
+1 -1
View File
@@ -77,7 +77,7 @@ class _AppDrawerState extends State<AppDrawer> {
applicationName: 'Universal BLE',
applicationVersion:
"${snapshot.data?.version} (${snapshot.data?.buildNumber})",
applicationLegalese: '\u{a9} 2025 Navideck',
applicationLegalese: '\u{a9} 2023 Navideck',
aboutBoxChildren: [
const SizedBox(height: 24),
RichText(
@@ -32,6 +32,7 @@ class _PeripheralDetailPageState extends State<PeripheralDetailPage> {
bool _isDeviceInfoExpanded = false;
bool _isDeviceActionsExpanded = true;
final Map<String, bool> _subscribedCharacteristics = {};
bool _autoConnect = false;
StreamSubscription? connectionStreamSubscription;
StreamSubscription? pairingStateSubscription;
@@ -688,59 +689,155 @@ class _PeripheralDetailPageState extends State<PeripheralDetailPage> {
Widget _buildConnectDisconnectButton() {
final colorScheme = Theme.of(context).colorScheme;
return SizedBox(
width: double.infinity,
child: Padding(
padding: const EdgeInsets.symmetric(
horizontal: 16.0,
vertical: 8.0,
),
child: ElevatedButton.icon(
onPressed: () async {
if (isConnected) {
await _executeWithLoading(
() async {
await bleDevice.disconnect();
_addLog("DisconnectResult", true);
},
onError: (error) {
_addLog('DisconnectError', error);
},
);
} else {
await _executeWithLoading(
() async {
await bleDevice.connect();
_addLog("ConnectionResult", true);
},
onError: (error) {
_addLog(
'ConnectError (${error.runtimeType})',
error,
);
},
);
}
},
icon: Icon(
isConnected ? Icons.bluetooth_disabled : Icons.bluetooth_connected,
size: 20,
),
label: Text(isConnected ? 'Disconnect' : 'Connect'),
style: ElevatedButton.styleFrom(
backgroundColor:
isConnected ? colorScheme.error : colorScheme.primary,
foregroundColor:
isConnected ? colorScheme.onError : colorScheme.onPrimary,
padding: const EdgeInsets.symmetric(
vertical: 16,
),
return Padding(
padding: const EdgeInsets.symmetric(
horizontal: 16.0,
vertical: 8.0,
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// AutoConnect toggle
Card(
elevation: 1,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
elevation: 2,
color: colorScheme.surfaceContainerHighest,
child: Padding(
padding: const EdgeInsets.symmetric(
horizontal: 12,
vertical: 8,
),
child: Row(
children: [
Icon(
Icons.autorenew,
size: 18,
color: colorScheme.primary,
),
const SizedBox(width: 8),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Auto Reconnect',
style: TextStyle(
fontSize: 13,
fontWeight: FontWeight.w600,
color: colorScheme.onSurface,
),
),
Text(
'Automatically reconnect when device becomes available',
style: TextStyle(
fontSize: 11,
color: colorScheme.onSurface.withValues(alpha: 0.7),
),
),
],
),
),
Switch(
value: _autoConnect,
onChanged: (value) async {
// If toggling off auto-connect, disconnect to prevent unwanted reconnections
if (!value && _autoConnect) {
if (isConnected) {
// Device is connected, disconnect it to prevent auto-reconnect
await _executeWithLoading(
() async {
await bleDevice.disconnect();
_addLog("DisconnectResult",
"Disconnected to disable auto-reconnect");
},
onError: (error) {
_addLog('DisconnectError', error);
},
);
} else {
// Device is already disconnected, but call disconnect() anyway
// to ensure cleanup and prevent any pending auto-reconnection attempts
await _executeWithLoading(
() async {
await bleDevice.disconnect();
_addLog("DisconnectResult",
"Cleanup performed to prevent auto-reconnect");
},
onError: (error) {
_addLog('DisconnectError', error);
},
);
}
}
setState(() {
_autoConnect = value;
});
},
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
),
],
),
),
),
),
const SizedBox(height: 12),
// Connect/Disconnect button
SizedBox(
width: double.infinity,
child: ElevatedButton.icon(
onPressed: () async {
if (isConnected) {
await _executeWithLoading(
() async {
await bleDevice.disconnect();
_addLog("DisconnectResult", true);
},
onError: (error) {
_addLog('DisconnectError', error);
},
);
} else {
await _executeWithLoading(
() async {
await bleDevice.connect(autoConnect: _autoConnect);
_addLog(
"ConnectionResult",
"Connected${_autoConnect ? ' (Auto-reconnect enabled)' : ''}",
);
},
onError: (error) {
_addLog(
'ConnectError (${error.runtimeType})',
error,
);
},
);
}
},
icon: Icon(
isConnected
? Icons.bluetooth_disabled
: Icons.bluetooth_connected,
size: 20,
),
label: Text(isConnected ? 'Disconnect' : 'Connect'),
style: ElevatedButton.styleFrom(
backgroundColor:
isConnected ? colorScheme.error : colorScheme.primary,
foregroundColor:
isConnected ? colorScheme.onError : colorScheme.onPrimary,
padding: const EdgeInsets.symmetric(
vertical: 16,
),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
elevation: 2,
),
),
),
],
),
);
}
+1 -1
View File
@@ -9,7 +9,7 @@ import 'package:universal_ble_example/home/scanner_screen.dart';
Future<bool> initializeApp() async {
WidgetsFlutterBinding.ensureInitialized();
await StorageService.instance.init();
// await UniversalBle.setLogLevel(BleLogLevel.verbose);
await UniversalBle.setLogLevel(BleLogLevel.verbose);
return await UniversalBle.hasPermissions(
withAndroidFineLocation: false,
);
@@ -11,4 +11,4 @@ PRODUCT_NAME = Universal BLE
PRODUCT_BUNDLE_IDENTIFIER = com.navideck.universalble
// The copyright displayed in application information
PRODUCT_COPYRIGHT = Copyright © 2025 com.navideck. All rights reserved.
PRODUCT_COPYRIGHT = Copyright © 2023 com.navideck. All rights reserved.
+2 -2
View File
@@ -18,12 +18,12 @@ package_rename_config:
macos:
app_name: "Universal BLE"
package_name: "com.navideck.universalble"
copyright_notice: "Copyright © 2025 com.navideck. All rights reserved."
copyright_notice: "Copyright © 2023 com.navideck. All rights reserved."
windows:
app_name: "Universal BLE"
organization: "Navideck"
copyright_notice: "Copyright © 2025 com.navideck. All rights reserved."
copyright_notice: "Copyright © 2023 com.navideck. All rights reserved."
exe_name: "universal_ble"
web:
+1 -1
View File
@@ -93,7 +93,7 @@ BEGIN
VALUE "FileDescription", "Universal BLE" "\0"
VALUE "FileVersion", VERSION_AS_STRING "\0"
VALUE "InternalName", "Universal BLE" "\0"
VALUE "LegalCopyright", "Copyright © 2025 com.navideck. All rights reserved." "\0"
VALUE "LegalCopyright", "Copyright © 2023 com.navideck. All rights reserved." "\0"
VALUE "OriginalFilename", "universal_ble.exe" "\0"
VALUE "ProductName", "Universal BLE" "\0"
VALUE "ProductVersion", VERSION_AS_STRING "\0"