bluetooth_connection_plugin 1.0.0
bluetooth_connection_plugin: ^1.0.0 copied to clipboard
A Flutter plugin for connecting to Bluetooth devices, specifically designed for weighing machines and similar devices. Supports device discovery, connection, data reading, and real-time weight monitoring.
example/lib/main.dart
import 'package:flutter/material.dart';
import 'package:bluetooth_connection_plugin/bluetooth_connection_plugin.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Bluetooth Connection Plugin Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
useMaterial3: true,
),
home: const BluetoothDemoScreen(),
);
}
}
class BluetoothDemoScreen extends StatefulWidget {
const BluetoothDemoScreen({super.key});
@override
State<BluetoothDemoScreen> createState() => _BluetoothDemoScreenState();
}
class _BluetoothDemoScreenState extends State<BluetoothDemoScreen> {
List<BluetoothDeviceInfo> pairedDevices = [];
bool isLoading = false;
bool isConnecting = false;
Map<String, dynamic>? connectedDevice;
String? connectingDeviceAddress;
List<String> receivedData = [];
String currentWeight = 'No weight data';
bool isGettingWeight = false;
@override
void initState() {
super.initState();
BluetoothConnectionPlugin.initialize();
}
Future<void> getPairedDevices() async {
setState(() {
isLoading = true;
});
try {
final List<Map<String, dynamic>> result =
await BluetoothConnectionPlugin.getPairedDevices();
setState(() {
pairedDevices =
result.map((item) => BluetoothDeviceInfo.fromMap(item)).toList();
isLoading = false;
});
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Found ${pairedDevices.length} paired devices'),
backgroundColor: Colors.green,
),
);
}
} on BluetoothException catch (e) {
setState(() {
isLoading = false;
});
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Error: ${e.message}'),
backgroundColor: Colors.red,
),
);
}
}
}
Future<void> connectToDevice(String deviceAddress, String deviceName) async {
setState(() {
isConnecting = true;
connectingDeviceAddress = deviceAddress;
});
try {
final Map<String, dynamic> result =
await BluetoothConnectionPlugin.connectToDevice(deviceAddress);
setState(() {
connectedDevice = result;
isConnecting = false;
connectingDeviceAddress = null;
});
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Connected to $deviceName'),
backgroundColor: Colors.green,
),
);
}
} on BluetoothException catch (e) {
setState(() {
isConnecting = false;
connectingDeviceAddress = null;
});
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Connection failed: ${e.message}'),
backgroundColor: Colors.red,
),
);
}
}
}
Future<void> disconnectDevice() async {
try {
await BluetoothConnectionPlugin.disconnectDevice();
setState(() {
connectedDevice = null;
receivedData.clear();
});
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Disconnected from device'),
backgroundColor: Colors.orange,
),
);
}
} on BluetoothException catch (e) {
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Disconnect failed: ${e.message}'),
backgroundColor: Colors.red,
),
);
}
}
}
Future<void> getWeight() async {
setState(() {
isGettingWeight = true;
});
try {
final BluetoothData result = await BluetoothConnectionPlugin.readData();
setState(() {
if (result.data.isNotEmpty) {
String cleanWeight = result.cleanWeight;
// Only add to receivedData if it's different from the last reading
String timestampStr = result.dateTime.toString().substring(11, 19);
String newEntry = '$timestampStr: $cleanWeight';
// Avoid duplicate consecutive readings
if (receivedData.isEmpty || receivedData.last != newEntry) {
currentWeight = cleanWeight;
receivedData.add(newEntry);
// Keep only last 20 readings to prevent UI clutter
if (receivedData.length > 20) {
receivedData.removeAt(0);
}
}
} else {
currentWeight = 'No data received';
}
isGettingWeight = false;
});
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(result.data.isNotEmpty
? 'Weight: ${result.cleanWeight}'
: 'No weight data received'),
backgroundColor:
result.data.isNotEmpty ? Colors.green : Colors.orange,
),
);
}
} on BluetoothException catch (e) {
setState(() {
currentWeight = 'Error reading weight';
isGettingWeight = false;
});
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Failed to get weight: ${e.message}'),
backgroundColor: Colors.red,
),
);
}
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Bluetooth Connection Plugin Demo'),
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
actions: [
if (connectedDevice != null)
IconButton(
icon: const Icon(Icons.bluetooth_connected),
onPressed: disconnectDevice,
tooltip: 'Disconnect',
),
],
),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: SingleChildScrollView(
child: Column(
children: [
// Connection status card
if (connectedDevice != null)
Card(
color: Colors.green.shade50,
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
children: [
Row(
children: [
const Icon(Icons.bluetooth_connected,
color: Colors.green),
const SizedBox(width: 8),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Connected to: ${connectedDevice!['deviceName']}',
style: const TextStyle(
fontWeight: FontWeight.bold),
),
Text(
'Address: ${connectedDevice!['deviceAddress']}'),
],
),
),
ElevatedButton(
onPressed: disconnectDevice,
style: ElevatedButton.styleFrom(
backgroundColor: Colors.red,
foregroundColor: Colors.white,
),
child: const Text('Disconnect'),
),
],
),
const SizedBox(height: 16),
// Data control buttons
Row(
children: [
ElevatedButton.icon(
onPressed: () {
setState(() {
receivedData.clear();
});
},
icon: const Icon(Icons.clear),
label: const Text('Clear'),
style: ElevatedButton.styleFrom(
backgroundColor: Colors.grey,
foregroundColor: Colors.white,
),
),
],
),
const SizedBox(height: 16),
// Weight section
Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
border: Border.all(color: Colors.grey.shade300),
borderRadius: BorderRadius.circular(8),
color: Colors.grey.shade50,
),
child: Row(
children: [
const Icon(Icons.monitor_weight,
color: Colors.blue),
const SizedBox(width: 8),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'Current Weight:',
style: TextStyle(
fontSize: 12,
color: Colors.grey,
),
),
Text(
currentWeight,
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
fontFamily: 'monospace',
),
),
],
),
),
const SizedBox(width: 8),
ElevatedButton.icon(
onPressed: isGettingWeight ? null : getWeight,
icon: isGettingWeight
? const SizedBox(
width: 16,
height: 16,
child: CircularProgressIndicator(
strokeWidth: 2),
)
: const Icon(Icons.refresh),
label: Text(isGettingWeight
? 'Getting...'
: 'Get Weight'),
style: ElevatedButton.styleFrom(
backgroundColor: Colors.blue,
foregroundColor: Colors.white,
),
),
],
),
),
],
),
),
),
const SizedBox(height: 16),
if (connectedDevice == null)
Center(
child: ElevatedButton(
onPressed: isLoading ? null : getPairedDevices,
child: isLoading
? const Row(
mainAxisSize: MainAxisSize.min,
children: [
SizedBox(
width: 20,
height: 20,
child:
CircularProgressIndicator(strokeWidth: 2),
),
SizedBox(width: 8),
Text('Loading...'),
],
)
: const Text('Get Paired Devices'),
),
),
const SizedBox(height: 20),
// Paired devices section
if (connectedDevice == null)
SizedBox(
height: 300,
child: pairedDevices.isEmpty
? const Center(
child: Text(
'No paired devices found.\nTap the button to scan for paired devices.',
textAlign: TextAlign.center,
style: TextStyle(fontSize: 16, color: Colors.grey),
),
)
: ListView.builder(
itemCount: pairedDevices.length,
itemBuilder: (context, index) {
final device = pairedDevices[index];
final isCurrentlyConnecting = isConnecting &&
connectingDeviceAddress == device.address;
return Card(
margin: const EdgeInsets.symmetric(vertical: 4),
child: ListTile(
leading: Icon(
Icons.bluetooth,
color: Colors.blue,
),
title: Text(
device.name,
style: const TextStyle(
fontWeight: FontWeight.bold),
),
subtitle: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('Address: ${device.address}'),
Text('Type: ${device.type}'),
],
),
trailing: isCurrentlyConnecting
? const SizedBox(
width: 20,
height: 20,
child: CircularProgressIndicator(
strokeWidth: 2),
)
: const Icon(Icons.arrow_forward_ios,
size: 16),
onTap: () {
if (!isConnecting) {
connectToDevice(
device.address, device.name);
}
},
),
);
},
),
),
// Data display section
if (connectedDevice != null && receivedData.isNotEmpty) ...[
const SizedBox(height: 16),
Card(
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
const Icon(Icons.data_usage, color: Colors.blue),
const SizedBox(width: 8),
Text(
'Received Data (${receivedData.length} messages)',
style: const TextStyle(
fontWeight: FontWeight.bold,
fontSize: 16,
),
),
],
),
const SizedBox(height: 8),
Container(
height: 200,
width: double.infinity,
decoration: BoxDecoration(
border: Border.all(color: Colors.grey.shade300),
borderRadius: BorderRadius.circular(8),
),
child: ListView.builder(
itemCount: receivedData.length,
itemBuilder: (context, index) {
return Padding(
padding: const EdgeInsets.symmetric(
horizontal: 8,
vertical: 2,
),
child: Text(
receivedData[index],
style: const TextStyle(
fontFamily: 'monospace',
fontSize: 12,
),
),
);
},
),
),
],
),
),
),
],
],
),
),
),
);
}
}