bluetooth_serial_android 1.1.2 copy "bluetooth_serial_android: ^1.1.2" to clipboard
bluetooth_serial_android: ^1.1.2 copied to clipboard

PlatformAndroid

Flutter plugin for Classic Bluetooth (Serial RFCOMM) on Android. Enables device scanning, pairing, connection, and serial communication.

example/lib/main.dart

import 'dart:async';
import 'package:flutter/material.dart';
import 'package:bluetooth_serial_android/bluetooth_serial_android.dart';

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  final ok = await FlutterBluetoothSerial.ensurePermissions();
  debugPrint('Permissões Bluetooth: ${ok ? "OK" : "Aguardando autorização"}');
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});
  @override
  Widget build(BuildContext context) => const MaterialApp(
        debugShowCheckedModeBanner: false,
        home: BluetoothPage(),
      );
}

class BluetoothPage extends StatefulWidget {
  const BluetoothPage({super.key});
  @override
  State<BluetoothPage> createState() => _BluetoothPageState();
}

class _BluetoothPageState extends State<BluetoothPage> {
  // Dispositivos
  List<Map<String, String>> devices = [];
  bool scanning = false;

  // Conexão
  bool connected = false;
  String? connectedAddress;

  // Config de conexão
  final _uuidCtrl =
      TextEditingController(text: '00001101-0000-1000-8000-00805F9B34FB');
  final _timeoutCtrl = TextEditingController(text: '200');

  // Envio / Leitura
  final _sendCtrl = TextEditingController();
  String lineEnding = '\n'; // '\n', '\r', '\r\n' ou ''
  bool readingLoop = false;
  final List<String> logs = [];

  // ----------------------------------------------------------
  // Scan
  // ----------------------------------------------------------
  Future<void> _scan() async {
    setState(() {
      scanning = true;
      devices.clear();
    });
    try {
      final result = await FlutterBluetoothSerial.scanDevices();
      setState(() => devices = result);
    } catch (e) {
      _toast('Falha no scan: $e');
    } finally {
      setState(() => scanning = false);
    }
  }

  // ----------------------------------------------------------
  // Conectar / Desconectar
  // ----------------------------------------------------------
  Future<void> _connect(String addr) async {
    final uuid = _uuidCtrl.text.trim().isEmpty
        ? '00001101-0000-1000-8000-00805F9B34FB'
        : _uuidCtrl.text.trim();

    final timeout = int.tryParse(_timeoutCtrl.text.trim()) ?? 200;

    try {
      final ok = await FlutterBluetoothSerial.connect(
        addr,
        uuid: uuid,
        timeoutMs: timeout,
      );
      if (ok) {
        setState(() {
          connected = true;
          connectedAddress = addr;
        });
        _toast('Conectado com $addr');
      } else {
        _toast('Falha na conexão');
      }
    } catch (e) {
      _toast('Erro ao conectar: $e');
    }
  }

  Future<void> _disconnect() async {
    try {
      await FlutterBluetoothSerial.disconnect();
    } finally {
      setState(() {
        connected = false;
        connectedAddress = null;
        readingLoop = false;
      });
      _toast('Desconectado');
    }
  }

  // ----------------------------------------------------------
  // Leitura (uma vez)
  // ----------------------------------------------------------
  Future<void> _readOnce() async {
    if (!connected) return;
    try {
      final data = await FlutterBluetoothSerial.read();
      final msg = data ?? '<null/timeout>';
      _pushLog('[read] $msg');
    } catch (e) {
      _pushLog('[read] erro: $e');
    }
  }

  // Leitura de linha (uma vez) com delimitador
  Future<void> _readLineOnce() async {
    if (!connected) return;
    final delimiter = lineEnding; // usa a seleção atual
    try {
      final data = await FlutterBluetoothSerial.readLine(delimiter);
      final msg = data ?? '<null/timeout/sem linha completa>';
      _pushLog('[readLine] $msg');
    } catch (e) {
      _pushLog('[readLine] erro: $e');
    }
  }

  // ----------------------------------------------------------
  // Loop de leitura contínua (usando read())
  // ----------------------------------------------------------
  Future<void> _startReadLoop() async {
    if (!connected || readingLoop) return;
    setState(() => readingLoop = true);
    _pushLog('--- iniciar leitura contínua (read) ---');

    while (connected && readingLoop) {
      try {
        final data = await FlutterBluetoothSerial.read();
        if (data != null && data.isNotEmpty) {
          _pushLog('[loop] $data');
        }
      } catch (e) {
        _pushLog('[loop] erro: $e');
        break;
      }
      await Future.delayed(const Duration(milliseconds: 40));
    }

    _pushLog('--- loop de leitura encerrado ---');
    if (mounted) setState(() => readingLoop = false);
  }

  void _stopReadLoop() {
    if (!readingLoop) return;
    setState(() => readingLoop = false);
  }

  // ----------------------------------------------------------
  // Envio
  // ----------------------------------------------------------
  Future<void> _send() async {
    if (!connected) return;
    final msg = _sendCtrl.text;
    final full = msg + lineEnding;
    try {
      await FlutterBluetoothSerial.write(full);
      _pushLog('[write] "$msg" (fim: ${_endingLabel(lineEnding)})');
      _sendCtrl.clear();
    } catch (e) {
      _pushLog('[write] erro: $e');
    }
  }

  // ----------------------------------------------------------
  // UI helpers
  // ----------------------------------------------------------
  void _toast(String msg) {
    if (!mounted) return;
    ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(msg)));
  }

  void _pushLog(String line) {
    if (!mounted) return;
    setState(() {
      logs.add(line);
      if (logs.length > 1000) {
        logs.removeRange(0, logs.length - 1000);
      }
    });
  }

  String _endingLabel(String v) {
    if (v == '\n') return r'\n';
    if (v == '\r') return r'\r';
    if (v == '\r\n') return r'\r\n';
    return 'none';
  }

  // ----------------------------------------------------------
  // Build
  // ----------------------------------------------------------
  @override
  Widget build(BuildContext context) {
    final isConnected = connected && connectedAddress != null;

    return Scaffold(
      backgroundColor: Colors.white,
      appBar: AppBar(
        title: const Text('Bluetooth Serial (Exemplo)'),
        actions: [
          if (!isConnected)
            IconButton(
              icon: const Icon(Icons.refresh),
              tooltip: 'Buscar dispositivos',
              onPressed: _scan,
            ),
          if (isConnected)
            IconButton(
              icon: const Icon(Icons.link_off),
              tooltip: 'Desconectar',
              onPressed: _disconnect,
            ),
        ],
      ),
      body: Column(
        children: [
          // -----------------------------------------
          // Config de conexão
          // -----------------------------------------
          Container(
            color: const Color.fromARGB(255, 255, 255, 255),
            child: Padding(
              padding: const EdgeInsets.fromLTRB(12, 12, 12, 6),
              child: Row(
                children: [
                  Flexible(
                    flex: 3,
                    child: TextField(
                      controller: _uuidCtrl,
                      decoration: const InputDecoration(
                        labelText: 'UUID (SPP default)',
                        border: OutlineInputBorder(),
                        hintText: '00001101-0000-1000-8000-00805F9B34FB',
                      ),
                    ),
                  ),
                  const SizedBox(width: 8),
                  Expanded(
                    child: TextField(
                      controller: _timeoutCtrl,
                      keyboardType: TextInputType.number,
                      decoration: const InputDecoration(
                        labelText: 'Timeout (ms)',
                        border: OutlineInputBorder(),
                        hintText: '200',
                      ),
                    ),
                  ),
                ],
              ),
            ),
          ),

          // -----------------------------------------
          // Lista de dispositivos
          // -----------------------------------------
          if (scanning)
            const Padding(
              padding: EdgeInsets.all(16),
              child: CircularProgressIndicator(),
            )
          else
            Flexible(
              child: SizedBox(
                height: 180,
                child: ListView.builder(
                  itemCount: devices.length,
                  itemBuilder: (context, i) {
                    final d = devices[i];
                    final addr = d['address'] ?? '';
                    final isThis = addr == connectedAddress;
                    return ListTile(
                      dense: true,
                      title: Text(d['name'] ?? 'Sem nome'),
                      subtitle: Text(addr),
                      tileColor:
                          isThis ? Colors.lightBlue.withOpacity(0.25) : null,
                      onTap: isConnected ? null : () => _connect(addr),
                      trailing: isThis
                          ? const Icon(Icons.link, color: Colors.blue)
                          : null,
                    );
                  },
                ),
              ),
            ),

          const Divider(height: 1),

          // -----------------------------------------
          // Controles de leitura e envio
          // -----------------------------------------
          Container(
            color: const Color.fromARGB(255, 255, 255, 255),
            child: Padding(
              padding: const EdgeInsets.fromLTRB(12, 8, 12, 6),
              child: Row(
                children: [
                  const Text('Fim de linha: '),
                  const SizedBox(width: 8),
                  DropdownButton<String>(
                    value: lineEnding,
                    onChanged: (v) => setState(() => lineEnding = v ?? ''),
                    items: const [
                      DropdownMenuItem(value: '\n', child: Text(r'\n')),
                      DropdownMenuItem(value: '\r', child: Text(r'\r')),
                      DropdownMenuItem(value: '\r\n', child: Text(r'\r\n')),
                      DropdownMenuItem(value: '', child: Text('none')),
                    ],
                  ),
                ],
              ),
            ),
          ),
          Container(
            color: const Color.fromARGB(255, 255, 255, 255),
            child: Row(
              children: [
                ElevatedButton(
                  onPressed: isConnected ? _readOnce : null,
                  child: const Text('read() 1x'),
                ),
                const SizedBox(width: 8),
                ElevatedButton(
                  onPressed: isConnected ? _readLineOnce : null,
                  child: const Text('readLine() 1x'),
                ),
              ],
            ),
          ),

          Container(
            color: const Color.fromARGB(255, 255, 255, 255),
            child: Padding(
              padding: const EdgeInsets.fromLTRB(12, 0, 12, 6),
              child: Row(
                children: [
                  Expanded(
                    child: TextField(
                      controller: _sendCtrl,
                      enabled: isConnected,
                      decoration: const InputDecoration(
                        labelText: 'Mensagem',
                        border: OutlineInputBorder(),
                      ),
                      onSubmitted: (_) => _send(),
                    ),
                  ),
                  const SizedBox(width: 8),
                  ElevatedButton(
                    onPressed: isConnected ? _send : null,
                    child: const Text('Enviar'),
                  ),
                ],
              ),
            ),
          ),

          Container(
            color: const Color.fromARGB(255, 255, 255, 255),
            child: Padding(
              padding: const EdgeInsets.fromLTRB(12, 0, 12, 8),
              child: Row(
                children: [
                  ElevatedButton.icon(
                    icon: const Icon(Icons.play_arrow),
                    onPressed:
                        (isConnected && !readingLoop) ? _startReadLoop : null,
                    label: const Text('Iniciar leitura contínua (read)'),
                  ),
                  const SizedBox(width: 8),
                  ElevatedButton.icon(
                    icon: const Icon(Icons.stop),
                    onPressed: readingLoop ? _stopReadLoop : null,
                    label: const Text('Parar'),
                  ),
                ],
              ),
            ),
          ),

          const Divider(height: 1),

          // -----------------------------------------
          // Logs recebidos
          // -----------------------------------------
          Container(
            color: const Color.fromARGB(255, 250, 250, 250),
            child: Flexible(
              child: Padding(
                padding: const EdgeInsets.fromLTRB(12, 8, 12, 4),
                child: Row(
                  children: [
                    const Text('Logs recebidos:'),
                    const SizedBox(width: 8),
                    if (connectedAddress != null)
                      Text(
                        '[$connectedAddress]',
                        style: const TextStyle(fontStyle: FontStyle.italic),
                      ),
                  ],
                ),
              ),
            ),
          ),

          Flexible(
            child: Container(
              width: double.infinity,
              color: const Color(0xFFF6F8FA),
              child: ListView.builder(
                padding:
                    const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
                itemCount: logs.length,
                itemBuilder: (context, i) => Text(
                  logs[i],
                  style: const TextStyle(fontFamily: 'monospace'),
                ),
              ),
            ),
          ),
        ],
      ),
    );
  }

  @override
  void dispose() {
    _uuidCtrl.dispose();
    _timeoutCtrl.dispose();
    _sendCtrl.dispose();
    super.dispose();
  }
}
1
likes
160
points
134
downloads

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

Flutter plugin for Classic Bluetooth (Serial RFCOMM) on Android. Enables device scanning, pairing, connection, and serial communication.

Repository (GitHub)
View/report issues

Topics

#bluetooth #serial #android #spp #arduino

License

MIT (license)

Dependencies

flutter, plugin_platform_interface

More

Packages that depend on bluetooth_serial_android

Packages that implement bluetooth_serial_android