Flutter近场通信插件light_flutter_nearby_connections的使用

Flutter近场通信插件light_flutter_nearby_connections的使用

本项目是一个轻量级且自定义版本的此仓库。该项目基于Google Nearby Connections API,作为与原生Android和iOS API以及Flutter之间的接口。

特性

  • 对等设备发现
    • 广告
    • 浏览
  • 对等设备通信
    • 数据消息

示例代码

以下是使用light_flutter_nearby_connections插件的完整示例代码:

import 'dart:async';
import 'dart:convert';
import 'dart:developer';
import 'dart:io';
import 'package:device_info_plus/device_info_plus.dart';
import 'package:flutter_styled_toast/flutter_styled_toast.dart';
import 'package:flutter/material.dart';
import 'package:light_flutter_nearby_connections/light_flutter_nearby_connections.dart';
import 'package:permission_handler/permission_handler.dart';

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

Route<dynamic> generateRoute(RouteSettings settings) {
  switch (settings.name) {
    case '/':
      return MaterialPageRoute(builder: (_) => Home());
    case 'browser':
      return MaterialPageRoute(
          builder: (_) => DevicesListScreen(deviceType: DeviceType.browser));
    case 'advertiser':
      return MaterialPageRoute(
          builder: (_) => DevicesListScreen(deviceType: DeviceType.advertiser));
    default:
      return MaterialPageRoute(
          builder: (_) => Scaffold(
                body: Center(
                    child: Text('No route defined for ${settings.name}')),
              ));
  }
}

class MyApp extends StatelessWidget {
  [@override](/user/override)
  Widget build(BuildContext context) {
    return MaterialApp(
      onGenerateRoute: generateRoute,
      initialRoute: '/',
    );
  }
}

class Home extends StatelessWidget {
  [@override](/user/override)
  Widget build(BuildContext context) {
    return Scaffold(
      body: Column(
        children: [
          Expanded(
            child: InkWell(
              onTap: () {
                Navigator.pushNamed(context, 'browser');
              },
              child: Container(
                color: Colors.red,
                child: Center(
                    child: Text(
                  'BROWSER',
                  style: TextStyle(color: Colors.white, fontSize: 40),
                )),
              ),
            ),
          ),
          Expanded(
            child: InkWell(
              onTap: () {
                Navigator.pushNamed(context, 'advertiser');
              },
              child: Container(
                color: Colors.green,
                child: Center(
                    child: Text(
                  'ADVERTISER',
                  style: TextStyle(color: Colors.white, fontSize: 40),
                )),
              ),
            ),
          ),
        ],
      ),
    );
  }
}

enum DeviceType { advertiser, browser }

class DevicesListScreen extends StatefulWidget {
  const DevicesListScreen({required this.deviceType});

  final DeviceType deviceType;

  [@override](/user/override)
  _DevicesListScreenState createState() => _DevicesListScreenState();
}

class _DevicesListScreenState extends State<DevicesListScreen> {
  List<Device> devices = [];
  List<Device> connectedDevices = [];
  late NearbyService nearbyService;
  late StreamSubscription subscription;
  late StreamSubscription receivedDataSubscription;

  bool isInit = false;

  [@override](/user/override)
  void initState() {
    super.initState();
    init();
  }

  [@override](/user/override)
  void dispose() {
    subscription.cancel();
    receivedDataSubscription.cancel();
    nearbyService.stopBrowsingForPeers();
    nearbyService.stopAdvertisingPeer();
    super.dispose();
  }

  [@override](/user/override)
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
          title: Text(widget.deviceType.toString().substring(11).toUpperCase()),
        ),
        backgroundColor: Colors.white,
        body: ListView.builder(
            itemCount: getItemCount(),
            itemBuilder: (context, index) {
              final device = widget.deviceType == DeviceType.advertiser
                  ? connectedDevices[index]
                  : devices[index];
              return Container(
                margin: EdgeInsets.all(8.0),
                child: Column(
                  children: [
                    Row(
                      children: [
                        Expanded(
                            child: GestureDetector(
                          onTap: () => _onTabItemListener(device),
                          child: Column(
                            children: [
                              Text(device.deviceName),
                              Text(
                                getStateName(device.state),
                                style: TextStyle(
                                    color: getStateColor(device.state)),
                              ),
                            ],
                            crossAxisAlignment: CrossAxisAlignment.start,
                          ),
                        )),
                        // 请求连接
                        GestureDetector(
                          onTap: () => _onButtonClicked(device),
                          child: Container(
                            margin: EdgeInsets.symmetric(horizontal: 8.0),
                            padding: EdgeInsets.all(8.0),
                            height: 35,
                            width: 100,
                            color: getButtonColor(device.state),
                            child: Center(
                              child: Text(
                                getButtonStateName(device.state),
                                style: TextStyle(
                                    color: Colors.white,
                                    fontWeight: FontWeight.bold),
                              ),
                            ),
                          ),
                        )
                      ],
                    ),
                    SizedBox(
                      height: 8.0,
                    ),
                    Divider(
                      height: 1,
                      color: Colors.grey,
                    )
                  ],
                ),
              );
            }));
  }

  String getStateName(SessionState state) {
    switch (state) {
      case SessionState.notConnected:
        return "断开连接";
      case SessionState.connecting:
        return "等待中";
      default:
        return "已连接";
    }
  }

  Future<void> askPermissions() async {
    final info = await DeviceInfoPlugin().androidInfo;
    var withPermissions = true;
    var errors = [];
    final explicitPermissions = [
      Permission.locationWhenInUse, // 始终?
      Permission.location, // 始终?
      // Android 12及以上
      if (info.version.sdkInt >= 31) Permission.bluetoothAdvertise,
      if (info.version.sdkInt >= 31) Permission.bluetoothConnect,
      if (info.version.sdkInt >= 31) Permission.bluetoothScan,
      // Android 13及以上
      if (info.version.sdkInt >= 33) Permission.nearbyWifiDevices,
    ];
    try {
      if (explicitPermissions.isNotEmpty) {
        final other = await explicitPermissions.request();
        final locationStatus = await Permission.location.status;
        if (!locationStatus.isGranted) {
          errors.add("位置未启用");
        }
        final otherPermissions = !other.values.any((element) => !element.isGranted);
        withPermissions &= otherPermissions;
        if (!otherPermissions) {
          errors.add("某些权限未授予");
        }
        log("requestPermissions granted: $other");
      }
    } catch (e) {
      errors.add("请求权限时发生错误");
    }
    if (errors.isNotEmpty) {
      log("ERROR: requestPermissions failed: $errors");
      showDialog(
          context: context,
          builder: (context) => AlertDialog(
                content: Text("错误:\n\n${errors.join("\n")}"),
              ));
    }
  }

  String getButtonStateName(SessionState state) {
    switch (state) {
      case SessionState.notConnected:
      case SessionState.connecting:
        return "连接";
      default:
        return "断开连接";
    }
  }

  Color getStateColor(SessionState state) {
    switch (state) {
      case SessionState.notConnected:
        return Colors.black;
      case SessionState.connecting:
        return Colors.grey;
      default:
        return Colors.green;
    }
  }

  Color getButtonColor(SessionState state) {
    switch (state) {
      case SessionState.notConnected:
      case SessionState.connecting:
        return Colors.green;
      default:
        return Colors.red;
    }
  }

  _onTabItemListener(Device device) {
    if (device.state == SessionState.connected) {
      showDialog(
          context: context,
          builder: (BuildContext context) {
            final myController = TextEditingController();
            return AlertDialog(
              title: Text("发送消息"),
              content: TextField(controller: myController),
              actions: [
                TextButton(
                  child: Text("取消"),
                  onPressed: () {
                    Navigator.of(context).pop();
                  },
                ),
                TextButton(
                  child: Text("发送"),
                  onPressed: () {
                    nearbyService.sendMessage(device.deviceId, myController.text);
                    myController.text = '';
                  },
                )
              ],
            );
          });
    }
  }

  int getItemCount() {
    if (widget.deviceType == DeviceType.advertiser) {
      return connectedDevices.length;
    } else {
      return devices.length;
    }
  }

  _onButtonClicked(Device device) {
    switch (device.state) {
      case SessionState.notConnected:
        nearbyService.invitePeer(
          deviceID: device.deviceId,
          deviceName: device.deviceName,
        );
        break;
      case SessionState.connected:
        nearbyService.disconnectPeer(deviceID: device.deviceId);
        break;
      case SessionState.connecting:
        break;
    }
  }

  void init() async {
    nearbyService = NearbyService();
    String devInfo = '';
    await askPermissions();
    DeviceInfoPlugin deviceInfo = DeviceInfoPlugin();
    if (Platform.isAndroid) {
      AndroidDeviceInfo androidInfo = await deviceInfo.androidInfo;
      devInfo = androidInfo.model;
    }
    if (Platform.isIOS) {
      IosDeviceInfo iosInfo = await deviceInfo.iosInfo;
      devInfo = iosInfo.localizedModel;
    }
    await nearbyService.init(
        serviceType: 'mpconn',
        deviceName: devInfo,
        strategy: Strategy.Wi_Fi_P2P,
        callback: (isRunning) async {
          if (isRunning) {
            if (widget.deviceType == DeviceType.browser) {
              await nearbyService.stopBrowsingForPeers();
              await Future.delayed(Duration(microseconds: 200));
              await nearbyService.startBrowsingForPeers();
            } else {
              await nearbyService.stopAdvertisingPeer();
              await nearbyService.stopBrowsingForPeers();
              await Future.delayed(Duration(microseconds: 200));
              await nearbyService.startAdvertisingPeer();
              await nearbyService.startBrowsingForPeers();
            }
          }
        });
    subscription = nearbyService.stateChangedSubscription(callback: (devicesList) {
      devicesList.forEach((element) {
        print(
            " deviceId: ${element.deviceId} | deviceName: ${element.deviceName} | state: ${element.state}");

        if (Platform.isAndroid) {
          if (element.state == SessionState.connected) {
            nearbyService.stopBrowsingForPeers();
          } else {
            nearbyService.startBrowsingForPeers();
          }
        }
      });

      setState(() {
        devices.clear();
        devices.addAll(devicesList);
        connectedDevices.clear();
        connectedDevices.addAll(devicesList
            .where((d) => d.state == SessionState.connected)
            .toList());
      });
    });

    receivedDataSubscription = nearbyService.dataReceivedSubscription(callback: (data) {
      print("dataReceivedSubscription: ${jsonEncode(data)}");
      showToast(jsonEncode(data),
          context: context,
          axis: Axis.horizontal,
          alignment: Alignment.center,
          position: StyledToastPosition.bottom);
    });
  }
}

更多关于Flutter近场通信插件light_flutter_nearby_connections的使用的实战教程也可以访问 https://www.itying.com/category-92-b0.html

1 回复

更多关于Flutter近场通信插件light_flutter_nearby_connections的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html


light_flutter_nearby_connections 是一个用于 Flutter 的插件,它允许开发者在其应用程序中实现近场通信(Nearby Connections)功能。这个插件使用了 Google 的 Nearby Connections API,支持设备之间的点对点连接(P2P),而无需连接到互联网。

主要功能

  • 发现附近的设备:查找并连接到附近的设备。
  • 发送和接收数据:在设备之间传输数据。
  • 支持多种连接策略:包括星型拓扑和点对点拓扑。
  • 跨平台支持:支持 Android 和 iOS 平台。

使用步骤

1. 添加依赖

首先,在 pubspec.yaml 文件中添加 light_flutter_nearby_connections 依赖:

dependencies:
  flutter:
    sdk: flutter
  light_flutter_nearby_connections: ^1.0.0

然后,运行 flutter pub get 安装依赖。

2. 初始化插件

在你的 Dart 代码中导入插件并初始化:

import 'package:light_flutter_nearby_connections/nearby_connections.dart';

Nearby nearby = Nearby();

3. 配置权限

确保在 AndroidManifest.xml 中添加以下权限:

<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
<uses-permission android:name="android.permission.INTERNET" />

对于 iOS,在 Info.plist 中添加以下权限:

<key>NSBluetoothAlwaysUsageDescription</key>
<string>We need Bluetooth access to connect to nearby devices</string>
<key>NSBluetoothPeripheralUsageDescription</key>
<string>We need Bluetooth access to connect to nearby devices</string>
<key>NSLocationAlwaysUsageDescription</key>
<string>We need location access to connect to nearby devices</string>
<key>NSLocationWhenInUseUsageDescription</key>
<string>We need location access to connect to nearby devices</string>

4. 启动设备发现

使用 startAdvertisingstartDiscovery 方法来启动设备发现和广告。

// 启动广告
nearby.startAdvertising(
  deviceName: 'MyDevice',
  strategy: Strategy.P2P_CLUSTER,
  onConnectionInitiated: (String id, ConnectionInfo info) {
    // 处理连接初始化
  },
  onConnectionResult: (String id, ConnectionResult result) {
    // 处理连接结果
  },
  onDisconnected: (String id) {
    // 处理断开连接
  },
);

// 启动发现
nearby.startDiscovery(
  deviceName: 'MyDevice',
  strategy: Strategy.P2P_CLUSTER,
  onEndpointFound: (String id, String name, String serviceId) {
    // 处理发现设备
  },
  onEndpointLost: (String id) {
    // 处理设备丢失
  },
);

5. 建立连接

onConnectionInitiated 回调中接受连接请求:

nearby.acceptConnection(
  id: id,
  onPayLoadRecieved: (String id, Payload payload) {
    // 处理接收到的数据
  },
  onPayloadTransferUpdate: (String id, PayloadTransferUpdate update) {
    // 处理数据传输更新
  },
);

6. 发送数据

使用 sendPayload 方法发送数据:

nearby.sendPayload(
  id: id,
  payload: Payload.bytes([1, 2, 3, 4]),
);

7. 断开连接

使用 disconnectFromEndpoint 方法断开连接:

nearby.disconnectFromEndpoint(id: id);
回到顶部