layrz_ble 1.3.0-beta.5 copy "layrz_ble: ^1.3.0-beta.5" to clipboard
layrz_ble: ^1.3.0-beta.5 copied to clipboard

A Flutter library for cross-platform Bluetooth Low Energy (BLE) communication, supporting Android, iOS, macOS, Windows, Linux, and web.

example/lib/main.dart

// ignore_for_file: use_build_context_synchronously

import 'dart:typed_data';

import 'package:flutter/material.dart';
import 'package:layrz_ble/layrz_ble.dart';
import 'package:layrz_icons/layrz_icons.dart';
import 'package:layrz_models/layrz_models.dart';
import 'package:layrz_theme/layrz_theme.dart';
import 'package:permission_handler/permission_handler.dart';

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

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      theme: generateLightTheme(),
      debugShowCheckedModeBanner: false,
      builder: (context, child) {
        return ThemedSnackbarMessenger(
          child: child ?? const SizedBox(),
        );
      },
      home: const HomePage(),
    );
  }
}

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

  @override
  State<HomePage> createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  final _ble = LayrzBle();
  Map<String, BleDevice> _devices = {};
  List<BleDevice> get _deviceList => _devices.values.toList();
  List<BleService> _services = [];

  bool _isScanning = false;
  bool _isLoading = false;
  bool _isAdvertising = false;
  BleDevice? _selectedDevice;

  String get serviceUuid => '00000000-0000-0000-0000-000000000001';
  String get readCharacteristic => '00000000-0000-0000-0000-000000000002';

  AppThemedAsset get logo => const AppThemedAsset(
        normal: 'https://cdn.layrz.com/resources/layrz/logo/normal.png',
        white: 'https://cdn.layrz.com/resources/layrz/logo/white.png',
      );
  AppThemedAsset get favicon => const AppThemedAsset(
        normal: 'https://cdn.layrz.com/resources/layrz/favicon/normal.png',
        white: 'https://cdn.layrz.com/resources/layrz/favicon/white.png',
      );

  int mtu = 512;
  final plugin = LayrzBle();

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

    _ble.onScan.listen((BleDevice device) {
      _devices[device.macAddress] = device;
      setState(() {});
    });

    if (ThemedPlatform.isAndroid) {
      _ble.onGattUpdate.listen((BleGattEvent event) {
        if (event is GattWriteRequest) {
          debugPrint('Received GATT write request: ${event.characteristicUuid}');

          debugPrint('\tSending success');
          _ble.respondWriteRequest(
            requestId: event.requestId,
            macAddress: event.macAddress,
            offset: event.offset,
            serviceUuid: event.serviceUuid,
            characteristicUuid: event.characteristicUuid,
            success: true,
          );
        } else if (event is GattReadRequest) {
          debugPrint('Received GATT read request: ${event.characteristicUuid}');

          _ble.respondReadRequest(
            requestId: event.requestId,
            macAddress: event.macAddress,
            offset: event.offset,
            serviceUuid: event.serviceUuid,
            characteristicUuid: event.characteristicUuid,
            data: Uint8List.fromList([0x01, 0x02, 0x03, 0x04]),
          );
        } else {
          debugPrint('Received GATT event: $event');
        }
      });
    }

    _ble.onEvent.listen((BleEvent event) {
      if (event is BleDisconnected) {
        debugPrint('Disconnected from device: ${event.macAddress}');
        _selectedDevice = null;
        _services = [];
      }
    });

    _ble.onNotify.listen((BleCharacteristicNotification notification) {
      debugPrint('Received notification: $notification');
    });
  }

  @override
  Widget build(BuildContext context) {
    return ThemedLayout(
      logo: logo,
      favicon: favicon,
      appTitle: 'Layrz BLE Example',
      enableNotifications: false,
      userDynamicAvatar: Avatar(
        type: AvatarType.icon,
        icon: LayrzIconsClasses.solarOutlineUser,
      ),
      body: SizedBox(
        width: double.infinity,
        child: Column(
          children: [
            Text(
              "Layrz BLE Example",
              style: Theme.of(context).textTheme.titleMedium,
            ),
            const SizedBox(height: 10),
            SingleChildScrollView(
              scrollDirection: Axis.horizontal,
              child: Row(
                mainAxisAlignment: MainAxisAlignment.center,
                children: [
                  ThemedButton(
                    isLoading: _isLoading,
                    labelText: 'Check capabilities',
                    color: Colors.blue,
                    onTap: () async {
                      setState(() => _isLoading = true);
                      if (ThemedPlatform.isAndroid) {
                        await Permission.location.request();
                        await Permission.locationWhenInUse.request();
                      }
                      if (!ThemedPlatform.isMacOS && !ThemedPlatform.isWeb) {
                        await Permission.bluetooth.request();
                      }

                      if (ThemedPlatform.isAndroid) {
                        await Permission.bluetooth.request();
                        await Permission.bluetoothScan.request();
                        await Permission.bluetoothConnect.request();
                        await Permission.bluetoothAdvertise.request();
                      }

                      bool result = await plugin.checkCapabilities();
                      ThemedSnackbarMessenger.of(context).showSnackbar(
                        ThemedSnackbar(
                          message: 'Capabilities: $result',
                          color: Colors.blue,
                          icon: LayrzIcons.solarOutlineBluetoothSquare,
                          maxLines: 5,
                        ),
                      );

                      await Future.delayed(const Duration(milliseconds: 20));

                      result = await plugin.checkScanPermissions();
                      ThemedSnackbarMessenger.of(context).showSnackbar(
                        ThemedSnackbar(
                          message: 'Scan: $result',
                          color: Colors.blue,
                          icon: LayrzIcons.solarOutlineBluetoothSquare,
                          maxLines: 5,
                        ),
                      );

                      await Future.delayed(const Duration(milliseconds: 20));

                      result = await plugin.checkAdvertisePermissions();
                      ThemedSnackbarMessenger.of(context).showSnackbar(
                        ThemedSnackbar(
                          message: 'Advertise: $result',
                          color: Colors.blue,
                          icon: LayrzIcons.solarOutlineBluetoothSquare,
                          maxLines: 5,
                        ),
                      );

                      setState(() => _isLoading = false);
                    },
                  ),
                  if (_selectedDevice != null) ...[
                    const SizedBox(width: 10),
                    ThemedButton(
                      isLoading: _isLoading,
                      labelText: 'Disconnect device',
                      color: Colors.red,
                      onTap: () async {
                        setState(() => _isLoading = true);
                        final result = await plugin.disconnect();

                        if (result == true) {
                          _selectedDevice = null;
                        }

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

                        ThemedSnackbarMessenger.of(context).showSnackbar(ThemedSnackbar(
                          message: 'Disconnected from device',
                          color: Colors.red,
                          icon: LayrzIcons.solarOutlineBluetoothSquare,
                        ));
                      },
                    ),
                  ] else ...[
                    if (ThemedPlatform.isAndroid) ...[
                      if (!_isAdvertising) ...[
                        const SizedBox(width: 10),
                        ThemedButton(
                          isLoading: _isLoading,
                          labelText: 'Start BLE Advertise',
                          color: Colors.green,
                          onTap: () async {
                            setState(() => _isLoading = true);
                            _devices = {};
                            _isAdvertising = await plugin.startAdvertise(
                              manufacturerData: [
                                const BleManufacturerData(
                                  companyId: 0x1234,
                                  data: [0x00, 0x01, 0x02, 0x03],
                                ),
                              ],
                              serviceData: [
                                const BleServiceData(
                                  uuid: 0x1234,
                                  data: [0xff],
                                ),
                              ],
                              canConnect: true,
                              allowBluetooth5: true,
                              servicesSpecs: [
                                BleService(
                                  uuid: serviceUuid,
                                  characteristics: [
                                    BleCharacteristic(
                                      uuid: readCharacteristic,
                                      properties: [
                                        BleProperty.read,
                                        BleProperty.notify,
                                      ],
                                    ),
                                    const BleCharacteristic(
                                      uuid: '00000000-0000-0000-0000-000000000003',
                                      properties: [BleProperty.write],
                                    ),
                                  ],
                                )
                              ],
                            );
                            setState(() => _isLoading = false);

                            ThemedSnackbarMessenger.of(context).showSnackbar(ThemedSnackbar(
                              message: 'Scanning for BLE devices...',
                              color: Colors.blue,
                              icon: LayrzIcons.solarOutlineBluetoothSquare,
                            ));
                          },
                        ),
                      ] else ...[
                        const SizedBox(width: 10),
                        ThemedButton(
                          isLoading: _isLoading,
                          labelText: 'Stop BLE Advertise',
                          color: Colors.red,
                          onTap: () async {
                            setState(() => _isLoading = true);
                            final result = await plugin.stopAdvertise();
                            debugPrint('Stop advertise result: $result');
                            if (result == true) {
                              _isAdvertising = false;
                            }

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

                            ThemedSnackbarMessenger.of(context).showSnackbar(ThemedSnackbar(
                              message: 'Advertise stopped',
                              color: Colors.red,
                              icon: LayrzIcons.solarOutlineBluetoothSquare,
                            ));
                          },
                        ),
                        const SizedBox(width: 10),
                        ThemedButton(
                          isLoading: _isLoading,
                          labelText: 'Send Service update',
                          color: Colors.orange,
                          onTap: () async {
                            setState(() => _isLoading = true);
                            final result = await plugin.sendNotification(
                              serviceUuid: serviceUuid,
                              characteristicUuid: readCharacteristic,
                              payload: Uint8List.fromList([0x04, 0x03, 0x02, 0x01, 0x05]),
                              requestConfirmation: false,
                            );
                            debugPrint('Send notification result: $result');
                            setState(() => _isLoading = false);
                          },
                        ),
                      ],
                    ],
                    if (!_isScanning) ...[
                      const SizedBox(width: 10),
                      ThemedButton(
                        isLoading: _isLoading,
                        labelText: 'Start BLE scan',
                        color: Colors.green,
                        onTap: () async {
                          setState(() => _isLoading = true);
                          _devices = {};
                          _isScanning = await plugin.startScan() ?? false;
                          setState(() => _isLoading = false);

                          ThemedSnackbarMessenger.of(context).showSnackbar(ThemedSnackbar(
                            message: 'Scanning for BLE devices...',
                            color: Colors.blue,
                            icon: LayrzIcons.solarOutlineBluetoothSquare,
                          ));
                        },
                      ),
                    ] else ...[
                      const SizedBox(width: 10),
                      ThemedButton(
                        isLoading: _isLoading,
                        labelText: 'Stop BLE scan',
                        color: Colors.red,
                        onTap: () async {
                          setState(() => _isLoading = true);
                          final result = await plugin.stopScan();
                          debugPrint('Stop scan result: $result');
                          if (result == true) {
                            _isScanning = false;
                          }

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

                          ThemedSnackbarMessenger.of(context).showSnackbar(ThemedSnackbar(
                            message: 'Scan stopped',
                            color: Colors.red,
                            icon: LayrzIcons.solarOutlineBluetoothSquare,
                          ));
                        },
                      ),
                    ],
                  ],
                ],
              ),
            ),
            const SizedBox(height: 10),
            if (_selectedDevice == null) ...[
              Expanded(
                child: ListView.builder(
                  itemCount: _deviceList.length,
                  itemBuilder: (context, index) {
                    final device = _deviceList[index];
                    return InkWell(
                      onTap: () async {
                        debugPrint('Selected device: ${device.macAddress}');
                        setState(() => _isLoading = true);
                        final result = await plugin.connect(macAddress: device.macAddress);
                        if (result == true) {
                          _selectedDevice = device;
                          _services = [];
                        }
                        setState(() => _isLoading = false);

                        ThemedSnackbarMessenger.of(context).showSnackbar(ThemedSnackbar(
                          message: 'Connected to device: ${device.macAddress}',
                          color: Colors.green,
                          icon: LayrzIcons.solarOutlineBluetoothSquare,
                        ));
                      },
                      child: Padding(
                        padding: const EdgeInsets.all(8.0),
                        child: Row(
                          children: [
                            ThemedAvatar(
                              icon: LayrzIcons.solarOutlineIPhone,
                              size: 40,
                            ),
                            const SizedBox(width: 10),
                            Expanded(
                              child: Column(
                                crossAxisAlignment: CrossAxisAlignment.start,
                                children: [
                                  Text(
                                    device.name ?? 'Unknown device',
                                    style: Theme.of(context).textTheme.titleSmall,
                                  ),
                                  Text(
                                    device.macAddress,
                                    style: Theme.of(context).textTheme.bodySmall,
                                  ),
                                  Text(
                                    'RSSI: ${device.rssi} - TX power: ${device.txPower}',
                                    style: Theme.of(context).textTheme.bodySmall,
                                  ),
                                  Text(
                                    "Manufacturer data: ${_castManufaturerData(device.manufacturerData)}",
                                    style: Theme.of(context).textTheme.bodySmall,
                                    maxLines: 10,
                                  ),
                                  Text(
                                    "Service data: ${_castServiceData(device.serviceData)}",
                                    style: Theme.of(context).textTheme.bodySmall,
                                    maxLines: 10,
                                  ),
                                ],
                              ),
                            ),
                          ],
                        ),
                      ),
                    );
                  },
                ),
              ),
            ] else ...[
              Row(
                mainAxisAlignment: MainAxisAlignment.center,
                children: [
                  ThemedButton(
                    color: Colors.blue,
                    labelText: 'Discover services',
                    isLoading: _isLoading,
                    onTap: () async {
                      // setState(() => _isLoading = true);
                      // _services = await plugin.discoverServices() ?? [];
                      // setState(() => _isLoading = false);

                      // ThemedSnackbarMessenger.of(context).showSnackbar(ThemedSnackbar(
                      //   message: 'Discovered ${_services.length} services',
                      //   color: Colors.blue,
                      //   icon: LayrzIcons.solarOutlineBluetoothSquare,
                      // ));
                    },
                  ),
                  const SizedBox(width: 10),
                  ThemedButton(
                    color: Colors.orange,
                    labelText: 'Set MTU to 512',
                    isLoading: _isLoading,
                    onTap: () async {
                      // setState(() => _isLoading = true);
                      // final result = await plugin.setMtu(newMtu: 512);
                      // debugPrint('Set MTU result: $result');
                      // setState(() => _isLoading = false);

                      // if (result != null) {
                      //   mtu = result;
                      //   setState(() {});
                      // }

                      // ThemedSnackbarMessenger.of(context).showSnackbar(ThemedSnackbar(
                      //   message: 'Set MTU to $result after a negotiation',
                      //   color: Colors.orange,
                      //   icon: LayrzIcons.solarOutlineBluetoothSquare,
                      // ));
                    },
                  ),
                ],
              ),
              const SizedBox(height: 10),
              // Row(
              //   mainAxisAlignment: MainAxisAlignment.center,
              //   children: [
              //     ThemedButton(
              //       color: Colors.orange,
              //       labelText: 'Set notification listener',
              //       isLoading: _isLoading,
              //       onTap: () async {
              //         setState(() => _isLoading = true);
              //         final result = await plugin.startNotify(
              //           serviceUuid: serviceId,
              //           characteristicUuid: readCharacteristic,
              //         );
              //         debugPrint('Set notification listener result: $result');
              //         setState(() => _isLoading = false);

              //         ThemedSnackbarMessenger.of(context).showSnackbar(ThemedSnackbar(
              //           message: 'Notification listener set: $result',
              //           color: Colors.orange,
              //           icon: LayrzIcons.solarOutlineBluetoothSquare,
              //         ));
              //       },
              //     ),
              //     const SizedBox(width: 10),
              //     ThemedButton(
              //       color: Colors.orange,
              //       labelText: 'Set notification listener off',
              //       isLoading: _isLoading,
              //       onTap: () async {
              //         setState(() => _isLoading = true);
              //         final result = await plugin.stopNotify(
              //           serviceUuid: serviceId,
              //           characteristicUuid: readCharacteristic,
              //         );
              //         debugPrint('Set notification listener result: $result');
              //         setState(() => _isLoading = false);

              //         ThemedSnackbarMessenger.of(context).showSnackbar(ThemedSnackbar(
              //           message: 'Notification listener set: $result',
              //           color: Colors.orange,
              //           icon: LayrzIcons.solarOutlineBluetoothSquare,
              //         ));
              //       },
              //     ),
              //     const SizedBox(width: 10),
              //     ThemedButton(
              //       color: Colors.blue,
              //       labelText: 'Send a payload',
              //       isLoading: _isLoading,
              //       onTap: () async {
              //         setState(() => _isLoading = true);

              //         debugPrint("Sending header");
              //         await plugin.writeCharacteristic(
              //           serviceUuid: serviceId,
              //           characteristicUuid: writeCharacteristic,
              //           payload: Uint8List.fromList("##${payload.length};1".codeUnits),
              //           withResponse: true,
              //         );

              //         debugPrint("Sending payload");
              //         await plugin.writeCharacteristic(
              //           serviceUuid: serviceId,
              //           characteristicUuid: writeCharacteristic,
              //           payload: Uint8List.fromList(payload.codeUnits),
              //           withResponse: true,
              //         );

              //         setState(() => _isLoading = false);

              //         ThemedSnackbarMessenger.of(context).showSnackbar(ThemedSnackbar(
              //           message: 'Payload sent',
              //           color: Colors.blue,
              //           icon: LayrzIcons.solarOutlineBluetoothSquare,
              //         ));

              //         final result = await plugin.readCharacteristic(
              //           serviceUuid: serviceId,
              //           characteristicUuid: readCharacteristic,
              //         );

              //         debugPrint('Read characteristic result: ${ascii.decode(result?.toList() ?? [])}');
              //       },
              //     ),
              //   ],
              // ),
              const SizedBox(height: 10),
              Expanded(
                child: SingleChildScrollView(
                  child: Column(
                    children: _services.map((service) {
                      return Column(
                        children: [
                          Text(
                            'Service: ${service.uuid}',
                            style: Theme.of(context).textTheme.titleSmall,
                          ),
                          const SizedBox(height: 5),
                          Text(
                            'Characteristics:',
                            style: Theme.of(context).textTheme.bodySmall,
                          ),
                          Padding(
                            padding: const EdgeInsets.only(left: 10),
                            child: Column(
                              children: (service.characteristics ?? []).map((characteristic) {
                                return Column(
                                  children: [
                                    Text(
                                      'Characteristic: ${characteristic.uuid}',
                                      style: Theme.of(context).textTheme.bodySmall,
                                    ),
                                    const SizedBox(height: 5),
                                    Text(
                                      'Properties: ${characteristic.properties}',
                                      style: Theme.of(context).textTheme.bodySmall,
                                    ),
                                  ],
                                );
                              }).toList(),
                            ),
                          ),
                        ],
                      );
                    }).toList(),
                  ),
                ),
              ),
            ],
          ],
        ),
      ),
    );
  }

  String _castUintToString(List<int>? data) {
    if (data == null) return 'Not provided';
    if (data.isEmpty) return 'Empty';
    return data.map((e) => e.toRadixString(16).padLeft(2, '0')).join(' ');
  }

  String _castManufaturerData(List<BleManufacturerData> data) {
    if (data.isEmpty) return 'Empty';

    List<String> result = [];
    for (final manufacturerData in data) {
      result.add(
        'Company ID: ${manufacturerData.companyId.toRadixString(16).padLeft(4, '0')} - '
        'Data: ${_castUintToString(manufacturerData.data)}',
      );
    }

    return result.join('\n');
  }

  String _castServiceData(List<BleServiceData>? data) {
    if (data == null) return 'Not provided';
    if (data.isEmpty) return 'Empty';
    List<String> result = [];

    for (final serviceData in data) {
      result.add(
        'Service: ${serviceData.uuid.toRadixString(16).padLeft(4, '0')} - '
        'Data: ${_castUintToString(serviceData.data)}',
      );
    }
    return result.join('\n');
  }
}
4
likes
150
points
394
downloads

Publisher

verified publishergoldenm.com

Weekly Downloads

A Flutter library for cross-platform Bluetooth Low Energy (BLE) communication, supporting Android, iOS, macOS, Windows, Linux, and web.

Repository (GitHub)
View/report issues

License

MIT (license)

Dependencies

bluez, collection, flutter, flutter_web_bluetooth, flutter_web_plugins, freezed_annotation, json_annotation, layrz_models, plugin_platform_interface, web

More

Packages that depend on layrz_ble