Flutter自定义功能插件adhoc_plugin的使用
Flutter自定义功能插件adhoc_plugin的使用
Ad Hoc Library (adhoc_plugin)
adhoc_plugin
是一个用于处理Android移动设备上的Ad Hoc网络操作的Flutter插件。
该库是Dart版本的AdHocLibrary项目的移植版。原项目由Gaulthier Gain开发,支持蓝牙和Wi-Fi Direct,而移植版本仅支持蓝牙低功耗(Bluetooth Low Energy)和Wi-Fi Direct。一些类保持不变,仅进行了少量修改,例如Android Wi-Fi Direct API。原始项目可以在以下链接找到:AdHocLib。
该版本是为我在列日大学(Université de Liège)蒙泰菲奥里研究所(Montefiore Institute)的硕士学位论文设计的。除了移植原始项目外,还添加了一些新的安全功能,如向远程目标发送加密数据的能力。
使用方法
初始化库
要初始化库,可以按如下方式执行:
bool verbose = true;
TransferManager transferManager = TransferManager(verbose);
也可以通过配置Config
对象来修改库的行为:
bool verbose = false;
Config config = Config();
config.label = "Example name"; // 用于通信
config.public = true; // 可以加入任何组
TransferManager transferManager = TransferManager(verbose, config);
监听事件
由于在Ad Hoc网络中会发生不同的事件,可以通过监听TransferManager
暴露的广播流来获取这些事件。
TransferManager transferManager = TransferManager(false);
void _listen() {
_manager.eventStream.listen((event) {
switch (event.type) {
case AdHocType.onDeviceDiscovered:
var device = event.device as AdHocDevice;
break;
case AdHocType.onDiscoveryStarted:
break;
case AdHocType.onDiscoveryCompleted:
var discovered = event.data as Map<String?, AdHocDevice?>;
break;
case AdHocType.onDataReceived:
var data = event.data as Object;
break;
case AdHocType.onForwardData:
var data = event.data as Object;
break;
case AdHocType.onConnection:
var device = event.device as AdHocDevice;
break;
case AdHocType.onConnectionClosed:
var device = event.device as AdHocDevice;
break;
case AdHocType.onInternalException:
var exception = event.data as Exception;
break;
case AdHocType.onGroupInfo:
var info = event.data as int;
break;
case AdHocType.onGroupDataReceived:
var data = event.data as Object;
break;
default:
}
});
}
应用示例
音乐应用共享
这是一个展示如何使用库API的示例。
源代码
import 'dart:async';
import 'dart:collection';
import 'dart:io';
import 'dart:typed_data';
import 'package:adhoc_plugin/adhoc_plugin.dart';
import 'package:analyzer_plugin/utilities/pair.dart';
import 'package:file_picker/file_picker.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:path_provider/path_provider.dart';
import 'search_bar.dart';
void main() => runApp(AdHocMusicClient());
enum MenuOptions { add, search, display }
const platform = MethodChannel('adhoc.music.player/main');
class AdHocMusicClient extends StatefulWidget {
[@override](/user/override)
_AdHocMusicClientState createState() => _AdHocMusicClientState();
}
class _AdHocMusicClientState extends State<AdHocMusicClient> {
static const PLAYLIST = 0;
static const REQUEST = 1;
static const REPLY = 2;
static const TRANSFER = 3;
static const NONE = 'none';
final TransferManager _manager = TransferManager(true);
final List<AdHocDevice> _discovered = List.empty(growable: true);
final List<AdHocDevice> _peers = List.empty(growable: true);
final List<Pair<String, String>> _playlist = List.empty(growable: true);
final HashMap<String, HashMap<String, PlatformFile?>> _globalPlaylist = HashMap();
final HashMap<String, PlatformFile?> _localPlaylist = HashMap();
final HashMap<String, bool> _isTransfering = HashMap();
final Set<String> timestamps = <String>{};
bool _requested = false;
bool _display = false;
String? _selected = NONE;
[@override](/user/override)
void initState() {
super.initState();
_manager.enableBle(3600);
_manager.eventStream.listen(_processAdHocEvent);
_manager.open = true;
}
[@override](/user/override)
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: Builder(
builder: (context) => Scaffold(
appBar: AppBar(
centerTitle: true,
title: const Text('Ad Hoc Music Client'),
actions: <Widget>[
PopupMenuButton<MenuOptions>(
onSelected: (result) async {
switch (result) {
case MenuOptions.add:
await _openFileExplorer();
break;
case MenuOptions.search:
var songs = List<String>.empty(growable: true);
_localPlaylist.entries.map((entry) => songs.add(entry.key));
_selected = (await showSearch(
context: context,
delegate: SearchBar(songs),
))!;
if (_selected == null) {
_selected = NONE;
}
break;
case MenuOptions.display:
setState(() => _display = !_display);
break;
}
},
itemBuilder: (context) => <PopupMenuEntry<MenuOptions>>[
const PopupMenuItem<MenuOptions>(
value: MenuOptions.add,
child: ListTile(
leading: Icon(Icons.playlist_add),
title: Text('Add song to playlist'),
),
),
const PopupMenuItem<MenuOptions>(
value: MenuOptions.search,
child: ListTile(
leading: Icon(Icons.search),
title: Text('Search song'),
),
),
const PopupMenuItem<MenuOptions>(
value: MenuOptions.display,
child: ListTile(
leading: Icon(Icons.music_note),
title: Text('Switch view'),
),
),
],
),
],
),
body: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Expanded(
child: Column(
children: <Widget>[
if (!_display) ...<Widget>[
Card(child: ListTile(title: Center(child: Text('Ad Hoc Peers')))),
ElevatedButton(
child: Center(child: Text('Search for nearby devices')),
onPressed: _manager.discovery,
),
Expanded(
child: ListView(
children: _discovered.map((device) {
var type = device.mac.ble == '' ? 'Wi-Fi' : 'BLE';
var mac = device.mac.ble == '' ? device.mac.wifi : device.mac.ble;
if (device.mac.ble != '' && device.mac.wifi != '') {
type = 'Wi-Fi/BLE';
mac = '${device.mac.wifi}/${device.mac.ble}';
}
return Card(
child: ListTile(
title: Center(child: Text(device.name!)),
subtitle: Center(child: Text('$type: $mac')),
onTap: () async {
await _manager.connect(device);
setState(() => _discovered.removeWhere((element) => (element.mac == device.mac)));
},
),
);
}).toList(),
),
),
] else ...<Widget>[
Card(child: Stack(
children: <Widget> [
ListTile(
title: Center(child: Text('$_selected')),
subtitle: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
IconButton(
icon: Icon(Icons.play_arrow_rounded),
onPressed: _play,
),
IconButton(
icon: Icon(Icons.pause_rounded),
onPressed: _pause,
),
IconButton(
icon: Icon(Icons.stop_rounded),
onPressed: _stop,
),
if (_requested)
Container(child: Center(child: CircularProgressIndicator()))
else
Container()
],
),
),
],
)),
Card(
color: Colors.blue,
child: ListTile(
title: Center(
child: const Text('Ad Hoc Playlist', style: TextStyle(color: Colors.white)),
),
),
),
Expanded(
child: ListView(
children: _playlist.map((pair) {
return Card(
child: ListTile(
title: Center(child: Text(pair.last)),
subtitle: Center(child: Text(pair.first)),
onTap: () => setState(() => _selected = pair.last),
),
);
}).toList(),
),
),
],
],
),
),
],
),
),
),
);
}
void _processAdHocEvent(Event event) {
switch (event.type) {
case AdHocType.onDeviceDiscovered:
break;
case AdHocType.onDiscoveryStarted:
break;
case AdHocType.onDiscoveryCompleted:
setState(() {
for (final discovered in (event.data as Map).values) {
_discovered.add(discovered as AdHocDevice);
}
});
break;
case AdHocType.onDataReceived:
_processDataReceived(event);
break;
case AdHocType.onForwardData:
_processDataReceived(event);
break;
case AdHocType.onConnection:
_peers.add(event.device as AdHocDevice);
break;
case AdHocType.onConnectionClosed:
break;
case AdHocType.onInternalException:
break;
case AdHocType.onGroupInfo:
break;
case AdHocType.onGroupDataReceived:
break;
default:
}
}
Future<void> _processDataReceived(Event event) async {
var peer = event.device;
var data = event.data as Map;
switch (data['type'] as int) {
case PLAYLIST:
var peers = data['peers'] as List;
var songs = data['songs'] as List;
var timestamp = data['timestamp'] as String;
if (timestamps.contains(timestamp)) {
break;
} else {
timestamps.add(timestamp);
}
var peerName = peers.first as String;
var entry = _globalPlaylist[peerName];
if (entry == null) {
entry = HashMap();
}
for (var i = 0; i < peers.length; i++) {
if (peerName == peers[i]) {
entry!.putIfAbsent(songs[i] as String, () => PlatformFile(name: songs[i] as String, size: 0));
} else {
_globalPlaylist[peerName] = entry!;
peerName = peers[i] as String;
entry = _globalPlaylist[peerName];
if (entry == null) {
entry = HashMap();
}
entry.putIfAbsent(songs[i] as String, () => PlatformFile(name: songs[i] as String, size: 0));
}
var pair = Pair<String, String>(peerName, songs[i] as String);
if (!_playlist.contains(pair)) {
_playlist.add(pair);
}
}
_globalPlaylist[peerName] = entry!;
setState(() {});
_manager.broadcastExcept(data, peer!);
break;
case REQUEST:
var name = data['name'] as String;
var found = false;
Uint8List? bytes;
PlatformFile? file;
if (_localPlaylist.containsKey(name)) {
found = true;
bytes = _localPlaylist[name]!.bytes!;
} else {
for (final entry in _globalPlaylist.entries) {
var _playlist = entry.value;
if (_playlist.containsKey(name)) {
file = _playlist[name];
if (file == null && file!.bytes == null) {
found = false;
break;
} else {
bytes = file.bytes;
if (bytes != null) {
found = true;
} else {
found = false;
}
break;
}
}
}
}
if (found == false) {
break;
} else {
var message = HashMap<String, dynamic>();
message = HashMap<String, dynamic>();
message.putIfAbsent('type', () => TRANSFER);
message.putIfAbsent('name', () => name);
_manager.sendMessageTo(message, peer!.label!);
message.clear();
message.putIfAbsent('type', () => REPLY);
message.putIfAbsent('name', () => name);
message.putIfAbsent('song', () => bytes);
_manager.sendMessageTo(message, peer.label!);
}
break;
case REPLY:
var name = data['name'] as String;
var song = Uint8List.fromList((data['song'] as List<dynamic>).cast<int>());
var tempDir = await getTemporaryDirectory();
var tempFile = File('${tempDir.path}/$name');
await tempFile.writeAsBytes(song, flush: true);
var entry = HashMap<String, PlatformFile>();
entry.putIfAbsent(
name, () => PlatformFile(
bytes: song, name: name, path: tempFile.path, size: song.length
)
);
_globalPlaylist.update(peer!.label!, (value) => entry, ifAbsent: () => entry);
setState(() => _requested = false);
break;
case TRANSFER:
var name = data['name'] as String;
_isTransfering.update(name, (value) => true, ifAbsent: () => true);
break;
default:
}
}
Future<void> _openFileExplorer() async {
var result = await FilePicker.platform.pickFiles(
allowMultiple: true,
type: FileType.audio,
);
if(result != null) {
for (var file in result.files) {
var bytes = await File(file.path!).readAsBytes();
var song = PlatformFile(
name: file.name,
path: file.path,
bytes: bytes,
size: bytes.length,
);
_localPlaylist.putIfAbsent(file.name, () => song);
var pair = Pair<String, String>(_manager.ownAddress, file.name);
if (!_playlist.contains(pair)) {
_playlist.add(pair);
}
}
}
_updatePlaylist();
}
void _updatePlaylist() async {
var peers = List<String>.empty(growable: true);
var songs = List<String>.empty(growable: true);
_globalPlaylist.forEach((peer, song) {
peers.add(peer);
song.forEach((key, value) {
songs.add(key);
});
});
_localPlaylist.forEach((name, file) {
peers.add(_manager.ownAddress);
songs.add(name);
});
var message = HashMap<String, dynamic>();
message.putIfAbsent('type', () => PLAYLIST);
message.putIfAbsent('peers', () => peers);
message.putIfAbsent('songs', () => songs);
message.putIfAbsent('timestamp', () => DateTime.now().toIso8601String());
_manager.broadcast(message);
}
void _play() {
if (_selected!.compareTo(NONE) == 0) {
return;
}
PlatformFile? file;
if (_localPlaylist.containsKey(_selected)) {
file = _localPlaylist[_selected];
} else {
_globalPlaylist.forEach((peerName, playlist) {
if (playlist.containsKey(_selected)) {
file = playlist[_selected];
if (file == null || file!.size == 0) {
var message = HashMap<String, dynamic>();
message.putIfAbsent('type', () => REQUEST);
message.putIfAbsent('name', () => _selected);
_manager.broadcast(message);
setState(() => _requested = true);
_isTransfering.putIfAbsent(_selected!, () => false);
Timer(Duration(seconds: 30), () {
if (_requested == true && _isTransfering[_selected] == false) {
_manager.sendMessageTo(message, peerName);
}
});
}
}
});
}
if (_requested == false) {
platform.invokeMethod('play', file!.path);
}
}
void _pause() {
if (_selected!.compareTo(NONE) == 0) {
return;
}
platform.invokeMethod('pause');
}
void _stop() {
if (_selected!.compareTo(NONE) == 0) {
return;
}
_selected = NONE;
platform.invokeMethod('stop');
setState(() {});
}
}
更多关于Flutter自定义功能插件adhoc_plugin的使用的实战教程也可以访问 https://www.itying.com/category-92-b0.html
更多关于Flutter自定义功能插件adhoc_plugin的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html
adhoc_plugin
是一个用于在 Flutter 应用中集成 AdHoc SDK 的自定义插件。AdHoc 是一个基于云端的 A/B 测试和功能开关管理平台,允许开发者在不发布新版本的情况下,动态调整应用的功能和配置。
要使用 adhoc_plugin
,你需要按照以下步骤进行设置和使用:
1. 添加依赖
首先,你需要在 pubspec.yaml
文件中添加 adhoc_plugin
依赖:
dependencies:
flutter:
sdk: flutter
adhoc_plugin: ^最新版本号 # 请替换为最新的版本号
然后,运行 flutter pub get
来获取依赖。
2. 初始化 AdHoc SDK
在你的 Flutter 应用的 main.dart
文件中,初始化 AdHoc SDK。通常,你会在 main
函数中进行初始化:
import 'package:adhoc_plugin/adhoc_plugin.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
// 初始化 AdHoc SDK
await AdhocPlugin.initialize(
appKey: '你的AppKey', // 从 AdHoc 平台获取的 AppKey
channel: 'your_channel', // 渠道名称
deviceId: 'your_device_id', // 设备 ID,可选
);
runApp(MyApp());
}
3. 使用 AdHoc 功能
3.1 获取实验变量
你可以通过 AdhocPlugin.getFlag
方法获取某个实验变量的值:
bool isFeatureEnabled = await AdhocPlugin.getFlag('your_flag_name', false);
print('Feature enabled: $isFeatureEnabled');
getFlag
方法的第一个参数是实验变量的名称,第二个参数是默认值(如果实验未启用或未找到变量时返回的值)。
3.2 设置用户属性
你可以通过 AdhocPlugin.setUserProperty
方法设置用户属性:
await AdhocPlugin.setUserProperty('user_id', '12345');
3.3 记录事件
你可以通过 AdhocPlugin.track
方法记录用户事件:
await AdhocPlugin.track('your_event_name');
4. 处理实验更新
你可以监听实验变量的更新,并在变量发生变化时执行相应的逻辑:
AdhocPlugin.onExperimentUpdate((experiment) {
print('Experiment updated: $experiment');
});
5. 调试和测试
在开发过程中,你可以启用 AdHoc 的调试模式,以便在控制台中查看日志:
await AdhocPlugin.setDebugEnabled(true);