simple_ble_plugin 0.0.91
simple_ble_plugin: ^0.0.91 copied to clipboard
simple_ble_plugin
example/lib/main.dart
import 'dart:typed_data';
import 'package:flutter/material.dart';
import 'dart:async';
import 'package:permission_handler/permission_handler.dart';
import 'package:simple_ble_plugin/simple_ble_plugin.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
home: HomeScreen(),
);
}
}
class HomeScreen extends StatefulWidget {
@override
_HomeScreenState createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
final _simpleBlePlugin = SimpleBlePlugin();
List<ScanResult> _scanResults = [];
bool _isScanning = false;
StreamSubscription? _scanSubscription;
@override
void initState() {
super.initState();
_scanSubscription = _simpleBlePlugin.scanResults.listen((result) {
setState(() {
final index = _scanResults.indexWhere((r) => r.device.id == result.device.id);
if (index != -1) {
_scanResults[index] = result;
} else {
_scanResults.add(result);
}
});
});
}
@override
void dispose() {
_scanSubscription?.cancel();
super.dispose();
}
Future<void> _startScan() async {
// Request permissions
Map<Permission, PermissionStatus> statuses = await [
Permission.bluetooth,
Permission.bluetoothScan,
Permission.bluetoothConnect,
Permission.location,
].request();
// Check if granted (simplified)
bool granted = true;
// logic depends on Android version, but generally...
// just try starting scan
setState(() {
_scanResults.clear();
_isScanning = true;
});
try {
await _simpleBlePlugin.startScan(
timeout: 10,
extended: true,
settings: ScanSettings(
scanMode: ScanMode.lowLatency,
allowDuplicates: true,
),
);
} catch (e) {
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text("Scan failed: $e")));
setState(() {
_isScanning = false;
});
}
}
}
Future<void> _stopScan() async {
await _simpleBlePlugin.stopScan();
setState(() {
_isScanning = false;
});
}
void _navigateToDevice(BleDevice device) {
_stopScan();
Navigator.of(context).push(MaterialPageRoute(
builder: (context) => DeviceScreen(device: device),
));
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('BLE Example'),
actions: [
if (_isScanning)
IconButton(icon: const Icon(Icons.stop), onPressed: _stopScan)
else
IconButton(icon: const Icon(Icons.refresh), onPressed: _startScan),
],
),
body: ListView.builder(
itemCount: _scanResults.length,
itemBuilder: (context, index) {
final result = _scanResults[index];
return ListTile(
title: Text(result.device.name ?? "Unknown Device"),
subtitle: Text("${result.device.id}\nRSSI: ${result.rssi}"),
onTap: () => _navigateToDevice(result.device),
);
},
),
);
}
}
class DeviceScreen extends StatefulWidget {
final BleDevice device;
const DeviceScreen({super.key, required this.device});
@override
_DeviceScreenState createState() => _DeviceScreenState();
}
class _DeviceScreenState extends State<DeviceScreen> {
final _blePlugin = SimpleBlePlugin();
bool _isConnected = false;
List<BleService> _services = [];
String? _status;
@override
void initState() {
super.initState();
_connect();
}
void _setStatus(String status) {
setState(() {
_status = status;
});
}
Future<void> _connect() async {
_setStatus("Connecting...");
try {
await _blePlugin.connect(widget.device.id);
if (!mounted) return;
setState(() {
_isConnected = true;
});
_setStatus("Connected. Discovering services...");
final services = await _blePlugin.discoverServices(widget.device.id);
if (!mounted) return;
setState(() {
_services = services;
_status = "Services found: ${services.length}";
});
} catch (e) {
if (!mounted) return;
_setStatus("Error: $e");
}
}
Future<void> _disconnect() async {
try {
await _blePlugin.disconnect(widget.device.id);
if (!mounted) return;
setState(() {
_isConnected = false;
_services = [];
_status = "Disconnected";
});
} catch(e) {
_setStatus("Error disconnecting: $e");
}
}
@override
void dispose() {
_disconnect();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.device.name ?? "Device"),
),
body: Column(
children: [
if (_status != null) Padding(padding: const EdgeInsets.all(8.0), child: Text(_status!)),
Expanded(
child: ListView.builder(
itemCount: _services.length,
itemBuilder: (context, index) {
final service = _services[index];
return ExpansionTile(
title: Text("Service ${service.uuid}"),
children: service.characteristics.map((c) => CharacteristicTile(
device: widget.device,
service: service,
characteristic: c
)).toList(),
);
},
),
),
],
),
);
}
}
class CharacteristicTile extends StatefulWidget {
final BleDevice device;
final BleService service;
final BleCharacteristic characteristic;
const CharacteristicTile({
required this.device,
required this.service,
required this.characteristic
});
@override
_CharacteristicTileState createState() => _CharacteristicTileState();
}
class _CharacteristicTileState extends State<CharacteristicTile> {
final _blePlugin = SimpleBlePlugin();
List<int>? _value;
String? _error;
StreamSubscription? _subscription;
bool _isMonitoring = false;
@override
void dispose() {
_subscription?.cancel();
super.dispose();
}
Future<void> _read() async {
setState(() { _error = null; });
try {
final val = await _blePlugin.readCharacteristic(
widget.device.id,
widget.service.uuid,
widget.characteristic.uuid
);
if (mounted) setState(() { _value = val; });
} catch (e) {
if (mounted) setState(() { _error = e.toString(); });
}
}
Future<void> _write() async {
// Example: Write 0x01
setState(() { _error = null; });
try {
await _blePlugin.writeCharacteristic(
widget.device.id,
widget.service.uuid,
widget.characteristic.uuid,
[0x01]
);
if (mounted) ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text("Write success")));
} catch (e) {
if (mounted) setState(() { _error = e.toString(); });
}
}
void _toggleMonitor() {
setState(() { _error = null; });
if (_isMonitoring) {
_subscription?.cancel();
setState(() { _isMonitoring = false; });
} else {
try {
final stream = _blePlugin.monitorCharacteristic(
widget.device.id,
widget.service.uuid,
widget.characteristic.uuid
);
_subscription = stream.listen((val) {
if (mounted) setState(() { _value = val; });
}, onError: (e) {
if (mounted) setState(() { _error = e.toString(); _isMonitoring = false; });
});
setState(() { _isMonitoring = true; });
} catch (e) {
setState(() { _error = e.toString(); });
}
}
}
@override
Widget build(BuildContext context) {
final props = widget.characteristic.properties;
final canRead = props.contains("READ");
final canWrite = props.contains("WRITE") || props.contains("WRITE_NO_RESPONSE");
final canNotify = props.contains("NOTIFY") || props.contains("INDICATE");
return ListTile(
title: Text("Char: ${widget.characteristic.uuid}"),
subtitle: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text("Props: ${props.join(', ')}"),
if (_value != null) Text("Value: ${_value!}"),
if (_error != null) Text("Error: $_error", style: TextStyle(color: Colors.red)),
],
),
trailing: Row(
mainAxisSize: MainAxisSize.min,
children: [
if (canRead)
IconButton(icon: Icon(Icons.download), onPressed: _read),
if (canWrite)
IconButton(icon: Icon(Icons.upload), onPressed: _write),
if (canNotify)
IconButton(
icon: Icon(_isMonitoring ? Icons.notifications_active : Icons.notifications_off),
onPressed: _toggleMonitor
),
],
),
);
}
}