flutter_bluetooth_classic_serial 1.0.3 copy "flutter_bluetooth_classic_serial: ^1.0.3" to clipboard
flutter_bluetooth_classic_serial: ^1.0.3 copied to clipboard

A Flutter plugin for Bluetooth Classic communication on Android, iOS, and Windows platforms. Supports device discovery, connection management, and data transmission.

Flutter Bluetooth Classic #

A Flutter plugin for Bluetooth Classic communication supporting Android, iOS, and Windows platforms.

Features #

  • 🔍 Device Discovery: Scan for and discover nearby Bluetooth Classic devices
  • 🔗 Connection Management: Connect and disconnect from Bluetooth devices
  • 📡 Data Transmission: Send and receive data over Bluetooth connections
  • 📱 Multi-Platform: Supports Android, iOS, and Windows
  • 🔄 Real-time Communication: Stream data for real-time applications

Platform Support #

Platform Support
Android
iOS
Windows
macOS
Linux
Web

Quick Start #

  1. Add the dependency:

    dependencies:
      flutter_bluetooth_classic_serial: ^1.0.1
    
  2. Import the package:

    import 'package:flutter_bluetooth_classic_serial/flutter_bluetooth_classic.dart';
    
  3. Initialize and use:

    FlutterBluetoothClassic bluetooth = FlutterBluetoothClassic();
       
    // Check if Bluetooth is available
    bool isSupported = await bluetooth.isBluetoothSupported();
    bool isEnabled = await bluetooth.isBluetoothEnabled();
       
    // Get paired devices
    List<BluetoothDevice> devices = await bluetooth.getPairedDevices();
       
    // Connect to a device
    bool connected = await bluetooth.connect(device.address);
       
    // Listen for data
    bluetooth.onDataReceived.listen((data) {
      print('Received: ${data.asString()}');
    });
       
    // Send message
    await bluetooth.sendString('Hello World!');
    

Example Project #

A complete example app is included in the example/ directory. To run it:

cd example
flutter run

The example demonstrates:

  • ✅ Bluetooth state management
  • ✅ Device discovery and connection
  • ✅ Real-time data communication
  • ✅ Auto-reconnection
  • ✅ Error handling
  • ✅ Modern Material 3 UI

Permissions #

Android #

Add these permissions to your android/app/src/main/AndroidManifest.xml:

<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />

<!-- For Android 12+ (API 31+) -->
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<uses-permission android:name="android.permission.BLUETOOTH_ADVERTISE" />

iOS #

Add to ios/Runner/Info.plist:

<key>NSBluetoothAlwaysUsageDescription</key>
<string>This app needs Bluetooth access to communicate with devices</string>
<key>NSBluetoothPeripheralUsageDescription</key>
<string>This app needs Bluetooth access to communicate with devices</string>

Windows #

Bluetooth capability is automatically included in the Windows implementation.

Usage #

Basic Example #

import 'package:flutter_bluetooth_classic_serial/flutter_bluetooth_classic.dart';

class BluetoothService {
  final FlutterBluetoothClassic _bluetooth = FlutterBluetoothClassic();
  StreamSubscription<BluetoothConnectionState>? _connectionSubscription;
  StreamSubscription<BluetoothData>? _dataSubscription;
  StreamSubscription<BluetoothState>? _stateSubscription;

  // Check if Bluetooth is supported
  Future<bool> isBluetoothSupported() async {
    try {
      return await _bluetooth.isBluetoothSupported();
    } catch (e) {
      print('Error checking Bluetooth support: $e');
      return false;
    }
  }

  // Check if Bluetooth is enabled
  Future<bool> isBluetoothEnabled() async {
    try {
      return await _bluetooth.isBluetoothEnabled();
    } catch (e) {
      print('Error checking Bluetooth status: $e');
      return false;
    }
  }

  // Get paired devices
  Future<List<BluetoothDevice>> getPairedDevices() async {
    try {
      return await _bluetooth.getPairedDevices();
    } catch (e) {
      print('Error getting paired devices: $e');
      return [];
    }
  }

  // Setup event listeners
  void setupListeners() {
    // Listen for Bluetooth state changes
    _stateSubscription = _bluetooth.onStateChanged.listen(
      (state) {
        print('Bluetooth state: ${state.isEnabled ? "enabled" : "disabled"}');
      },
    );

    // Listen for connection state changes
    _connectionSubscription = _bluetooth.onConnectionChanged.listen(
      (connectionState) {
        if (connectionState.isConnected) {
          print('Connected to ${connectionState.deviceAddress}');
        } else {
          print('Disconnected: ${connectionState.status}');
        }
      },
    );

    // Listen for incoming data
    _dataSubscription = _bluetooth.onDataReceived.listen(
      (data) {
        String received = data.asString();
        print('Received from ${data.deviceAddress}: $received');
      },
    );
  }

  // Connect to a device
  Future<bool> connectToDevice(String deviceAddress) async {
    try {
      return await _bluetooth.connect(deviceAddress);
    } catch (e) {
      print('Connection failed: $e');
      return false;
    }
  }

  // Send string data
  Future<bool> sendMessage(String message) async {
    try {
      return await _bluetooth.sendString(message);
    } catch (e) {
      print('Send failed: $e');
      return false;
    }
  }

  // Send raw data
  Future<bool> sendData(List<int> data) async {
    try {
      return await _bluetooth.sendData(data);
    } catch (e) {
      print('Send failed: $e');
      return false;
    }
  }

  // Disconnect
  Future<bool> disconnect() async {
    try {
      return await _bluetooth.disconnect();
    } catch (e) {
      print('Disconnect failed: $e');
      return false;
    }
  }

  // Start device discovery
  Future<bool> startDiscovery() async {
    try {
      return await _bluetooth.startDiscovery();
    } catch (e) {
      print('Discovery failed: $e');
      return false;
    }
  }

  // Stop device discovery
  Future<bool> stopDiscovery() async {
    try {
      return await _bluetooth.stopDiscovery();
    } catch (e) {
      print('Stop discovery failed: $e');
      return false;
    }
  }

  // Clean up resources
  void dispose() {
    _connectionSubscription?.cancel();
    _dataSubscription?.cancel();
    _stateSubscription?.cancel();
  }
}

Complete Example #

import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter_bluetooth_classic_serial/flutter_bluetooth_classic.dart';

class BluetoothScreen extends StatefulWidget {
  const BluetoothScreen({super.key});

  @override
  State<BluetoothScreen> createState() => _BluetoothScreenState();
}

class _BluetoothScreenState extends State<BluetoothScreen> {
  late FlutterBluetoothClassic _bluetooth;
  List<BluetoothDevice> _devices = [];
  BluetoothDevice? _connectedDevice;
  bool _isBluetoothEnabled = false;
  bool _isConnected = false;
  String _receivedData = '';
  final TextEditingController _messageController = TextEditingController();
  
  StreamSubscription<BluetoothConnectionState>? _connectionSubscription;
  StreamSubscription<BluetoothData>? _dataSubscription;
  StreamSubscription<BluetoothState>? _stateSubscription;

  @override
  void initState() {
    super.initState();
    _bluetooth = FlutterBluetoothClassic();
    _initBluetooth();
  }

  @override
  void dispose() {
    _connectionSubscription?.cancel();
    _dataSubscription?.cancel();
    _stateSubscription?.cancel();
    _messageController.dispose();
    super.dispose();
  }

  Future<void> _initBluetooth() async {
    // Check Bluetooth status
    bool isSupported = await _bluetooth.isBluetoothSupported();
    bool isEnabled = await _bluetooth.isBluetoothEnabled();
    
    setState(() {
      _isBluetoothEnabled = isEnabled;
    });

    if (isSupported && isEnabled) {
      await _loadDevices();
      _setupListeners();
    }
  }

  void _setupListeners() {
    // Listen for Bluetooth state changes
    _stateSubscription = _bluetooth.onStateChanged.listen((state) {
      setState(() {
        _isBluetoothEnabled = state.isEnabled;
      });
    });

    // Listen for connection changes
    _connectionSubscription = _bluetooth.onConnectionChanged.listen((connectionState) {
      setState(() {
        _isConnected = connectionState.isConnected;
        if (connectionState.isConnected) {
          _connectedDevice = _devices.firstWhere(
            (device) => device.address == connectionState.deviceAddress,
            orElse: () => BluetoothDevice(
              name: 'Unknown Device',
              address: connectionState.deviceAddress,
              paired: false,
            ),
          );
        } else {
          _connectedDevice = null;
        }
      });

      // Show connection status
      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(
          content: Text(connectionState.isConnected 
              ? 'Connected to ${_connectedDevice?.name}' 
              : 'Disconnected: ${connectionState.status}'),
          backgroundColor: connectionState.isConnected ? Colors.green : Colors.red,
        ),
      );
    });

    // Listen for incoming data
    _dataSubscription = _bluetooth.onDataReceived.listen((data) {
      setState(() {
        _receivedData += data.asString();
      });
    });
  }

  Future<void> _loadDevices() async {
    try {
      List<BluetoothDevice> devices = await _bluetooth.getPairedDevices();
      setState(() {
        _devices = devices;
      });
    } catch (e) {
      _showError('Failed to load devices: $e');
    }
  }

  Future<void> _connectToDevice(BluetoothDevice device) async {
    try {
      bool success = await _bluetooth.connect(device.address);
      if (!success) {
        _showError('Failed to connect to ${device.name}');
      }
    } catch (e) {
      _showError('Connection error: $e');
    }
  }

  Future<void> _disconnect() async {
    try {
      await _bluetooth.disconnect();
    } catch (e) {
      _showError('Disconnect error: $e');
    }
  }

  Future<void> _sendMessage() async {
    if (_messageController.text.isEmpty || !_isConnected) return;

    try {
      bool success = await _bluetooth.sendString(_messageController.text);
      if (success) {
        setState(() {
          _receivedData += 'Sent: ${_messageController.text}\n';
        });
        _messageController.clear();
      } else {
        _showError('Failed to send message');
      }
    } catch (e) {
      _showError('Send error: $e');
    }
  }

  void _showError(String message) {
    ScaffoldMessenger.of(context).showSnackBar(
      SnackBar(
        content: Text(message),
        backgroundColor: Colors.red,
      ),
    );
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Bluetooth Classic Demo'),
        actions: [
          IconButton(
            onPressed: _loadDevices,
            icon: const Icon(Icons.refresh),
          ),
        ],
      ),
      body: !_isBluetoothEnabled
          ? const Center(
              child: Text('Please enable Bluetooth'),
            )
          : Column(
              children: [
                // Connection Status
                Card(
                  margin: const EdgeInsets.all(8),
                  child: Padding(
                    padding: const EdgeInsets.all(16),
                    child: Row(
                      children: [
                        Icon(
                          _isConnected ? Icons.bluetooth_connected : Icons.bluetooth,
                          color: _isConnected ? Colors.green : Colors.grey,
                        ),
                        const SizedBox(width: 8),
                        Text(_isConnected 
                            ? 'Connected to ${_connectedDevice?.name}' 
                            : 'Not connected'),
                        const Spacer(),
                        if (_isConnected)
                          ElevatedButton(
                            onPressed: _disconnect,
                            child: const Text('Disconnect'),
                          ),
                      ],
                    ),
                  ),
                ),
                
                // Device List
                Expanded(
                  flex: 2,
                  child: Card(
                    margin: const EdgeInsets.all(8),
                    child: Column(
                      children: [
                        const Padding(
                          padding: EdgeInsets.all(8),
                          child: Text('Paired Devices', style: TextStyle(fontWeight: FontWeight.bold)),
                        ),
                        Expanded(
                          child: ListView.builder(
                            itemCount: _devices.length,
                            itemBuilder: (context, index) {
                              BluetoothDevice device = _devices[index];
                              bool isConnectedDevice = _connectedDevice?.address == device.address;
                              
                              return ListTile(
                                leading: Icon(
                                  Icons.bluetooth,
                                  color: isConnectedDevice ? Colors.green : null,
                                ),
                                title: Text(device.name),
                                subtitle: Text(device.address),
                                trailing: isConnectedDevice 
                                    ? const Icon(Icons.check, color: Colors.green)
                                    : ElevatedButton(
                                        onPressed: _isConnected ? null : () => _connectToDevice(device),
                                        child: const Text('Connect'),
                                      ),
                              );
                            },
                          ),
                        ),
                      ],
                    ),
                  ),
                ),

                // Message Input
                if (_isConnected)
                  Card(
                    margin: const EdgeInsets.all(8),
                    child: Padding(
                      padding: const EdgeInsets.all(16),
                      child: Row(
                        children: [
                          Expanded(
                            child: TextField(
                              controller: _messageController,
                              decoration: const InputDecoration(
                                hintText: 'Enter message',
                                border: OutlineInputBorder(),
                              ),
                              onSubmitted: (_) => _sendMessage(),
                            ),
                          ),
                          const SizedBox(width: 8),
                          ElevatedButton(
                            onPressed: _sendMessage,
                            child: const Text('Send'),
                          ),
                        ],
                      ),
                    ),
                  ),

                // Received Data
                Expanded(
                  flex: 1,
                  child: Card(
                    margin: const EdgeInsets.all(8),
                    child: Column(
                      children: [
                        Padding(
                          padding: const EdgeInsets.all(8),
                          child: Row(
                            children: [
                              const Text('Received Data', style: TextStyle(fontWeight: FontWeight.bold)),
                              const Spacer(),
                              IconButton(
                                onPressed: () => setState(() => _receivedData = ''),
                                icon: const Icon(Icons.clear),
                              ),
                            ],
                          ),
                        ),
                        Expanded(
                          child: Container(
                            width: double.infinity,
                            margin: const EdgeInsets.all(8),
                            padding: const EdgeInsets.all(8),
                            decoration: BoxDecoration(
                              border: Border.all(color: Colors.grey),
                              borderRadius: BorderRadius.circular(4),
                            ),
                            child: SingleChildScrollView(
                              child: Text(
                                _receivedData.isEmpty ? 'No data received' : _receivedData,
                                style: const TextStyle(fontFamily: 'monospace'),
                              ),
                            ),
                          ),
                        ),
                      ],                ),
                  ),
                ),
              ],
            ),
    );
  }
}

Testing with Hardware #

Arduino with HC-05/HC-06 #

void setup() {
  Serial.begin(9600);
}

void loop() {
  if (Serial.available()) {
    String data = Serial.readString();
    data.trim();
    Serial.print("Echo: ");
    Serial.println(data);
  }
  delay(100);
}

ESP32 Bluetooth Classic #

#include "BluetoothSerial.h"

BluetoothSerial SerialBT;

void setup() {
  Serial.begin(115200);
  SerialBT.begin("ESP32test"); // Bluetooth device name
  Serial.println("The device started, now you can pair it with bluetooth!");
}

void loop() {
  if (SerialBT.available()) {
    String message = SerialBT.readString();
    message.trim();
    SerialBT.print("Received: ");
    SerialBT.println(message);
    Serial.println("Sent response: " + message);
  }
  delay(20);
}

Common Issues & Solutions #

Permission Issues #

  • Android: Ensure all required permissions are added to AndroidManifest.xml
  • Location: Enable location services for device discovery (Android requirement)
  • Runtime: Request permissions at runtime for Android 6+

Connection Problems #

  • Pairing: Ensure device is paired before connecting
  • Range: Keep devices within Bluetooth range (typically 10 meters)
  • Interference: Avoid interference from other 2.4GHz devices
  • Multiple Connections: Most devices support only one active connection

Data Issues #

  • Encoding: Ensure both devices use the same text encoding (UTF-8 recommended)
  • Termination: Use consistent line endings (\n or \r\n)
  • Buffer: Implement proper buffering for large data transfers
  • Timing: Add appropriate delays between rapid data transmissions

Platform-Specific Notes #

Android:

  • Requires API level 21+ (Android 5.0)
  • Location permission needed for device discovery
  • Some devices may require specific UUIDs

iOS:

  • Limited to MFi-certified devices or accessories using standard profiles
  • May require additional entitlements for certain device types

Windows:

  • Requires Windows 10 build 1803 or later
  • Bluetooth adapter must support classic profiles

## API Reference

### FlutterBluetoothClassic

Main class for Bluetooth operations.

#### Constructor

```dart
FlutterBluetoothClassic bluetooth = FlutterBluetoothClassic();

Methods

Method Return Type Description
isBluetoothSupported() Future<bool> Check if Bluetooth is supported on the device
isBluetoothEnabled() Future<bool> Check if Bluetooth is currently enabled
enableBluetooth() Future<bool> Request to enable Bluetooth
getPairedDevices() Future<List<BluetoothDevice>> Get list of paired devices
startDiscovery() Future<bool> Start discovering nearby devices
stopDiscovery() Future<bool> Stop device discovery
connect(String address) Future<bool> Connect to device by address
disconnect() Future<bool> Disconnect from current device
sendData(List<int> data) Future<bool> Send raw byte data
sendString(String message) Future<bool> Send string message

Event Streams

Stream Type Description
onStateChanged Stream<BluetoothState> Bluetooth enable/disable events
onConnectionChanged Stream<BluetoothConnectionState> Connection status changes
onDataReceived Stream<BluetoothData> Incoming data from connected device

Data Models #

BluetoothDevice

Represents a Bluetooth device.

class BluetoothDevice {
  final String name;        // Device name
  final String address;     // Device MAC address
  final bool paired;        // Whether device is paired
}

BluetoothConnectionState

Represents connection status.

class BluetoothConnectionState {
  final bool isConnected;      // Connection status
  final String deviceAddress;  // Connected device address
  final String status;         // Status description
}

BluetoothData

Represents received data.

class BluetoothData {
  final String deviceAddress;  // Source device address
  final List<int> data;        // Raw byte data
  
  String asString();           // Convert data to string
}

BluetoothState

Represents Bluetooth adapter state.

class BluetoothState {
  final bool isEnabled;    // Whether Bluetooth is enabled
  final String status;     // Status description
}

BluetoothException

Exception thrown by Bluetooth operations.

class BluetoothException implements Exception {
  final String message;
}

Contributing #

Contributions are welcome! Please feel free to submit a Pull Request.

License #

This project is licensed under the MIT License - see the LICENSE file for details.

Support #

If you encounter any issues or have questions, please file an issue on our GitHub repository.

1
likes
145
points
125
downloads

Publisher

unverified uploader

Weekly Downloads

A Flutter plugin for Bluetooth Classic communication on Android, iOS, and Windows platforms. Supports device discovery, connection management, and data transmission.

Homepage
Repository (GitHub)
View/report issues

Topics

#bluetooth #bluetooth-classic #communication #serial #connectivity

Documentation

Documentation
API reference

License

MIT (license)

Dependencies

flutter, plugin_platform_interface

More

Packages that depend on flutter_bluetooth_classic_serial

Packages that implement flutter_bluetooth_classic_serial