ble_plugin 1.5.0 copy "ble_plugin: ^1.5.0" to clipboard
ble_plugin: ^1.5.0 copied to clipboard

A simple and easy-to-use ble plugin.

example/lib/main.dart

import 'dart:io';
import 'dart:typed_data';

import 'package:ble_plugin/ble_device.dart';
import 'package:ble_plugin/ble_plugin.dart';
import 'package:ble_plugin/event_channel_constant.dart';
import 'package:ble_plugin_example/data_provider.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

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

  @override
  State<MyApp> createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> implements DeviceListener, DataListener {
  final _blePlugin = BlePlugin.getInstance();

  List<BleDevice> deviceList = [];
  BleDevice? connectedDevice;

  List<String> logs = [];

  bool scanning = false;

  int copies = 5;

  String deviceId(BleDevice d) {
    return d.mac?.isNotEmpty == true ? d.mac! : "${d.name}_${d.hashCode}";
  }

  void addLog(String log) {
    debugPrint(log);

    setState(() {
      logs.insert(
        0,
        "[${DateTime.now().toString().substring(11, 19)}] $log",
      );

      if (logs.length > 200) {
        logs.removeLast();
      }
    });
  }

  @override
  void initState() {
    super.initState();

    _blePlugin.setDeviceListener(this);
    _blePlugin.setDataListener(this);
  }

  @override
  void dispose() {
    _blePlugin.stopListener();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    final bool isMac = Platform.isMacOS;

    return MaterialApp(
      debugShowCheckedModeBanner: false,
      theme: ThemeData(
        useMaterial3: true,
        scaffoldBackgroundColor: const Color(0xfff5f7fb),
      ),
      home: Scaffold(
        appBar: AppBar(
          elevation: 0,
          centerTitle: true,
          backgroundColor: Colors.white,
          title: const Text(
            "BLE Printer Demo",
            style: TextStyle(
              fontWeight: FontWeight.bold,
              color: Colors.black87,
            ),
          ),
        ),
        body: Center(
          child: ConstrainedBox(
            constraints: BoxConstraints(
              maxWidth: isMac ? 1100 : double.infinity,
            ),
            child: Column(
              children: [
                /// 顶部状态
                Container(
                  margin: const EdgeInsets.fromLTRB(12, 10, 12, 8),
                  padding: const EdgeInsets.all(14),
                  decoration: BoxDecoration(
                    color: Colors.white,
                    borderRadius: BorderRadius.circular(18),
                    boxShadow: [
                      BoxShadow(
                        color: Colors.black.withOpacity(0.04),
                        blurRadius: 10,
                      )
                    ],
                  ),
                  child: Row(
                    children: [
                      Container(
                        padding: const EdgeInsets.all(12),
                        decoration: BoxDecoration(
                          color: connectedDevice != null
                              ? Colors.green.withOpacity(0.12)
                              : Colors.blue.withOpacity(0.12),
                          borderRadius: BorderRadius.circular(14),
                        ),
                        child: Icon(
                          Icons.bluetooth,
                          color: connectedDevice != null
                              ? Colors.green
                              : Colors.blue,
                        ),
                      ),
                      const SizedBox(width: 14),
                      Expanded(
                        child: Column(
                          crossAxisAlignment: CrossAxisAlignment.start,
                          children: [
                            Text(
                              connectedDevice?.name ?? "未连接设备",
                              maxLines: 1,
                              overflow: TextOverflow.ellipsis,
                              style: const TextStyle(
                                fontWeight: FontWeight.bold,
                                fontSize: 16,
                              ),
                            ),
                            const SizedBox(height: 4),
                            Text(
                              connectedDevice != null ? "设备已连接" : "等待连接打印机",
                              style: const TextStyle(
                                color: Colors.grey,
                                fontSize: 13,
                              ),
                            ),
                          ],
                        ),
                      ),
                      if (connectedDevice != null)
                        FilledButton.tonal(
                          onPressed: () async {
                            await _blePlugin.disconnect();
                          },
                          child: const Text("断开"),
                        ),
                    ],
                  ),
                ),

                /// 操作栏
                Padding(
                  padding: const EdgeInsets.symmetric(horizontal: 12),
                  child: Wrap(
                    spacing: 10,
                    runSpacing: 10,
                    children: [
                      _actionBtn(
                        icon: Icons.search,
                        text: scanning ? "扫描中" : "扫描",
                        color: Colors.blue,
                        onTap: () async {
                          setState(() {
                            scanning = true;
                            deviceList.clear();
                          });

                          addLog("开始扫描");

                          await _blePlugin.startScanBluetooth();

                          /// macOS 自动停止
                          if (Platform.isMacOS) {
                            Future.delayed(
                              const Duration(seconds: 8),
                              () async {
                                await _blePlugin.stopScanBluetooth();

                                if (mounted) {
                                  setState(() {
                                    scanning = false;
                                  });
                                }

                                addLog("停止扫描");
                              },
                            );
                          }
                        },
                      ),
                      _actionBtn(
                        icon: Icons.stop_circle_outlined,
                        text: "停止",
                        color: Colors.orange,
                        onTap: () async {
                          await _blePlugin.stopScanBluetooth();

                          setState(() {
                            scanning = false;
                          });

                          addLog("手动停止扫描");
                        },
                      ),
                      _actionBtn(
                        icon: Icons.print,
                        text: "打印",
                        color: Colors.green,
                        onTap: printImage,
                      ),
                      _actionBtn(
                        icon: Icons.send,
                        text: "原始数据",
                        color: Colors.purple,
                        onTap: () async {
                          await _blePlugin.sendUint8Command(
                            Uint8List.fromList([
                              16,
                              255,
                              48,
                              17,
                            ]),
                          );

                          addLog("发送原始数据");
                        },
                      ),
                      _actionBtn(
                        icon: Icons.info_outline,
                        text: "状态",
                        color: Colors.teal,
                        onTap: showStatusDialog,
                      ),
                    ],
                  ),
                ),

                const SizedBox(height: 10),

                Padding(
                  padding: const EdgeInsets.symmetric(horizontal: 16),
                  child: Row(
                    children: [
                      const Icon(
                        Icons.devices,
                        size: 18,
                        color: Colors.black54,
                      ),
                      const SizedBox(width: 6),
                      Text(
                        "蓝牙设备 (${deviceList.length})",
                        style: const TextStyle(
                          fontWeight: FontWeight.bold,
                          fontSize: 15,
                        ),
                      ),
                    ],
                  ),
                ),

                const SizedBox(height: 8),

                Expanded(
                  child: Row(
                    children: [
                      /// 设备列表
                      Expanded(
                        flex: 2,
                        child: deviceList.isEmpty
                            ? _buildEmpty()
                            : ListView.builder(
                                padding: const EdgeInsets.only(
                                  bottom: 20,
                                ),
                                itemCount: deviceList.length,
                                itemBuilder: (_, index) {
                                  return _buildDeviceItem(
                                    deviceList[index],
                                  );
                                },
                              ),
                      ),

                      /// mac 日志
                      if (isMac)
                        Container(
                          width: 360,
                          margin: const EdgeInsets.only(
                            right: 12,
                            bottom: 12,
                          ),
                          decoration: BoxDecoration(
                            color: Colors.black,
                            borderRadius: BorderRadius.circular(16),
                          ),
                          child: Column(
                            children: [
                              Container(
                                padding: const EdgeInsets.all(12),
                                child: const Row(
                                  children: [
                                    Icon(
                                      Icons.terminal,
                                      color: Colors.white,
                                      size: 18,
                                    ),
                                    SizedBox(width: 8),
                                    Text(
                                      "运行日志",
                                      style: TextStyle(
                                        color: Colors.white,
                                        fontWeight: FontWeight.bold,
                                      ),
                                    ),
                                  ],
                                ),
                              ),
                              const Divider(
                                height: 1,
                                color: Colors.white24,
                              ),
                              Expanded(
                                child: ListView.builder(
                                  reverse: true,
                                  padding: const EdgeInsets.all(10),
                                  itemCount: logs.length,
                                  itemBuilder: (_, index) {
                                    return Padding(
                                      padding: const EdgeInsets.only(
                                        bottom: 4,
                                      ),
                                      child: Text(
                                        logs[index],
                                        style: const TextStyle(
                                          color: Colors.greenAccent,
                                          fontSize: 12,
                                        ),
                                      ),
                                    );
                                  },
                                ),
                              ),
                            ],
                          ),
                        ),
                    ],
                  ),
                ),
              ],
            ),
          ),
        ),
      ),
    );
  }

  Widget _actionBtn({
    required IconData icon,
    required String text,
    required Color color,
    required VoidCallback onTap,
  }) {
    return InkWell(
      borderRadius: BorderRadius.circular(12),
      onTap: onTap,
      child: Container(
        height: 44,
        padding: const EdgeInsets.symmetric(horizontal: 16),
        decoration: BoxDecoration(
          color: color.withOpacity(0.10),
          borderRadius: BorderRadius.circular(12),
        ),
        child: Row(
          mainAxisSize: MainAxisSize.min,
          children: [
            Icon(
              icon,
              size: 18,
              color: color,
            ),
            const SizedBox(width: 6),
            Text(
              text,
              style: TextStyle(
                color: color,
                fontWeight: FontWeight.w600,
                fontSize: 13,
              ),
            ),
          ],
        ),
      ),
    );
  }

  Widget _buildDeviceItem(BleDevice device) {
    bool isCurrent = connectedDevice != null &&
        deviceId(connectedDevice!) == deviceId(device);

    return Container(
      margin: const EdgeInsets.symmetric(
        horizontal: 12,
        vertical: 5,
      ),
      padding: const EdgeInsets.all(14),
      decoration: BoxDecoration(
        color: Colors.white,
        borderRadius: BorderRadius.circular(16),
        boxShadow: [
          BoxShadow(
            color: Colors.black.withOpacity(0.03),
            blurRadius: 8,
            offset: const Offset(0, 3),
          ),
        ],
      ),
      child: Row(
        children: [
          Container(
            padding: const EdgeInsets.all(12),
            decoration: BoxDecoration(
              color: isCurrent
                  ? Colors.green.withOpacity(0.12)
                  : Colors.blue.withOpacity(0.08),
              borderRadius: BorderRadius.circular(12),
            ),
            child: Icon(
              Icons.bluetooth,
              color: isCurrent ? Colors.green : Colors.blue,
            ),
          ),
          const SizedBox(width: 12),
          Expanded(
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                Text(
                  device.name.isEmpty ? "未知设备" : device.name,
                  maxLines: 1,
                  overflow: TextOverflow.ellipsis,
                  style: const TextStyle(
                    fontWeight: FontWeight.bold,
                    fontSize: 15,
                  ),
                ),
                const SizedBox(height: 4),
                Text(
                  device.mac?.isNotEmpty == true
                      ? device.mac!
                      : "macOS 无 MAC 地址",
                  style: const TextStyle(
                    color: Colors.grey,
                    fontSize: 12,
                  ),
                ),
              ],
            ),
          ),
          const SizedBox(width: 10),
          FilledButton(
            onPressed: () async {
              addLog("开始连接: ${device.name}");

              await _blePlugin.connect(device);
            },
            child: Text(
              isCurrent ? "已连接" : "连接",
            ),
          ),
        ],
      ),
    );
  }

  Widget _buildEmpty() {
    return Center(
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          Icon(
            Icons.bluetooth_disabled,
            size: 72,
            color: Colors.grey.shade400,
          ),
          const SizedBox(height: 12),
          Text(
            scanning ? "正在搜索设备..." : "暂无蓝牙设备",
            style: const TextStyle(
              color: Colors.grey,
              fontSize: 15,
            ),
          ),
        ],
      ),
    );
  }

  Future<void> showStatusDialog() async {
    bool isAvailable = await _blePlugin.isAvailable();

    bool isConnected = await _blePlugin.isConnected();

    bool isScanning = await _blePlugin.isScanning();

    bool isEnable = await _blePlugin.isEnable();

    BleDevice? device = await _blePlugin.getConnectedDevice();

    if (!mounted) return;

    showDialog(
      context: context,
      builder: (_) {
        return AlertDialog(
          title: const Text("设备状态"),
          content: Text(
            '''
蓝牙可用: $isAvailable
设备连接: $isConnected
正在扫描: $isScanning
蓝牙开启: $isEnable
连接设备: ${device?.name ?? '无'}
''',
          ),
        );
      },
    );
  }

  Future<void> printImage() async {
    if (copies < 1) return;

    addLog("开始打印");

    await _blePlugin.sendUint8TextCommand(
      DataProvider.colorImage(),
    );

    setState(() {
      copies--;
    });
  }

  @override
  void onFoundDevice(BleDevice device) {
    bool exists = deviceList.any(
      (e) => deviceId(e) == deviceId(device),
    );

    if (!exists) {
      setState(() {
        deviceList.add(device);
      });
    }

    addLog("发现设备 : ${device.name}");
  }

  @override
  void onReceivedData(Uint8List data) {
    addLog("收到打印机数据 : ${data.length} bytes");
  }

  @override
  void onConnectionStateChange(
    int state,
    BleDevice deviceBle,
  ) {
    switch (state) {
      case EventChannelConstant.STATE_CONNECTED:
        addLog("连接成功 : ${deviceBle.name}");
        break;

      case EventChannelConstant.STATE_DISCONNECTED:
        addLog("连接断开 : ${deviceBle.name}");
        break;

      case EventChannelConstant.STATE_FAILCONNECT:
        addLog("连接失败 : ${deviceBle.name}");
        break;
    }

    setState(() {
      connectedDevice =
          state == EventChannelConstant.STATE_CONNECTED ? deviceBle : null;
    });
  }

  @override
  void onBluetoothStateChange(int state) {
    switch (state) {
      case EventChannelConstant.BLUETOOTHON:
        addLog("蓝牙开启");
        break;

      case EventChannelConstant.BLUETOOTHOFF:
        addLog("蓝牙关闭");
        break;
    }
  }

  @override
  void onWriteEnd(int state) {
    addLog("数据写入完成");
  }
}
0
likes
115
points
411
downloads

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

A simple and easy-to-use ble plugin.

License

MIT (license)

Dependencies

flutter, flutter_web_plugins, plugin_platform_interface

More

Packages that depend on ble_plugin

Packages that implement ble_plugin