simple_ble_plugin 0.0.7 copy "simple_ble_plugin: ^0.0.7" to clipboard
simple_ble_plugin: ^0.0.7 copied to clipboard

simple_ble_plugin

example/lib/main.dart

import 'package:flutter/material.dart';
import 'dart:async';
import 'dart:convert';
import 'dart:typed_data';

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 _blePlugin = SimpleBlePlugin();
  List<ScanResult> _scanResults = [];
  bool _isScanning = false;
  StreamSubscription? _scanSubscription;

  @override
  void initState() {
    super.initState();
    _scanSubscription = _blePlugin.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 _blePlugin.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 _blePlugin.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;

  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(); });
    }
  }

  @override
  Widget build(BuildContext context) {
    return ListTile(
      title: Text("Char: ${widget.characteristic.uuid}"),
      subtitle: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          Text("Props: ${widget.characteristic.properties.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 (widget.characteristic.properties.contains("READ"))
            IconButton(icon: Icon(Icons.download), onPressed: _read),
          if (widget.characteristic.properties.contains("WRITE"))
            IconButton(icon: Icon(Icons.upload), onPressed: _write),
        ],
      ),
    );
  }
}