<think>我们使用Flutter来创建一个蓝牙连接App的工程。这个工程将包含以下功能:
1. 扫描附近的蓝牙设备
2. 显示扫描到的设备列表
3. 连接选定的蓝牙设备
4. 展示设备提供的服务与特征
5. 读写特征值(可选)
我们将使用flutter_blue_plus库,它是Flutter中一个功能强大的蓝牙库。
步骤:
1. 创建一个新的Flutter工程
2. 添加必要的依赖
3. 配置平台权限(Android和iOS)
4. 编写代码实现上述功能
下面是详细步骤:
## 1. 创建Flutter工程
在终端运行:
```bash
flutter create bluetooth_connect_app
cd bluetooth_connect_app
```
## 2. 添加依赖
打开`pubspec.yaml`文件,添加以下依赖:
```yaml
dependencies:
flutter:
sdk: flutter
flutter_blue_plus: ^1.8.3
permission_handler: ^11.3.1 # 用于权限请求
```
然后在终端运行:
```bash
flutter pub get
```
## 3. 配置平台权限
### Android配置
打开`android/app/src/main/AndroidManifest.xml`,在`<manifest>`标签内添加以下权限:
```xml
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<!-- 针对Android 12及以上版本 -->
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
```
在`<application>`标签内添加以下属性,以支持低版本Android:
```xml
<application
...
android:usesCleartextTraffic="true">
```
注意:在Android 12及以上版本中,还需要在`<application>`标签内添加:
```xml
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" android:usesPermissionFlags="neverForLocation" />
```
### iOS配置
打开`ios/Runner/Info.plist`,添加以下权限描述:
```xml
<dict>
...
<key>NSBluetoothAlwaysUsageDescription</key>
<string>需要蓝牙权限以连接设备</string>
<key>NSBluetoothPeripheralUsageDescription</key>
<string>需要蓝牙权限以连接设备</string>
<key>NSLocationWhenInUseUsageDescription</key>
<string>需要位置权限以发现蓝牙设备</string>
</dict>
```
## 4. 编写代码
我们将创建以下主要界面:
- 主界面:扫描设备,显示设备列表
- 设备服务界面:连接设备后,显示设备提供的服务和特征
- 特征操作界面:读写特征值(可选)
下面是主界面的代码示例:
```dart
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter_blue_plus/flutter_blue_plus.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(
title: '蓝牙连接App',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const BluetoothApp(),
);
}
}
class BluetoothApp extends StatefulWidget {
const BluetoothApp({super.key});
@override
State<BluetoothApp> createState() => _BluetoothAppState();
}
class _BluetoothAppState extends State<BluetoothApp> {
List<BluetoothDevice> _devicesList = [];
bool _isScanning = false;
StreamSubscription<List<ScanResult>>? _scanSubscription;
@override
void initState() {
super.initState();
_checkPermissions();
}
// 检查权限
Future<void> _checkPermissions() async {
if (await Permission.bluetoothScan.request().isGranted &&
await Permission.bluetoothConnect.request().isGranted) {
// 如果需要位置权限(Android 10以下)
if (!(await Permission.location.isGranted)) {
await Permission.location.request();
}
}
}
// 开始扫描
void _startScan() {
setState(() {
_isScanning = true;
_devicesList.clear();
});
// 先停止之前的扫描
FlutterBluePlus.stopScan();
// 开始新的扫描
_scanSubscription = FlutterBluePlus.scanResults.listen((results) {
for (ScanResult result in results) {
if (!_devicesList.contains(result.device)) {
setState(() {
_devicesList.add(result.device);
});
}
}
}, onError: (e) {
print("扫描出错: $e");
});
// 设置扫描超时
Future.delayed(const Duration(seconds: 15), () {
if (_isScanning) {
_stopScan();
}
});
// 开始扫描
FlutterBluePlus.startScan(timeout: const Duration(seconds: 15));
}
// 停止扫描
void _stopScan() {
_scanSubscription?.cancel();
FlutterBluePlus.stopScan();
setState(() {
_isScanning = false;
});
}
// 连接设备
void _connectToDevice(BluetoothDevice device) async {
try {
await device.connect();
Navigator.push(context, MaterialPageRoute(builder: (context) => DeviceScreen(device: device)));
} catch (e) {
print("连接失败: $e");
}
}
@override
void dispose() {
_scanSubscription?.cancel();
_stopScan();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('蓝牙设备扫描'),
actions: [
IconButton(
icon: Icon(_isScanning ? Icons.stop : Icons.search),
onPressed: () {
if (_isScanning) {
_stopScan();
} else {
_startScan();
}
},
)
],
),
body: ListView.builder(
itemCount: _devicesList.length,
itemBuilder: (context, index) {
BluetoothDevice device = _devicesList[index];
return ListTile(
title: Text(device.platformName),
subtitle: Text(device.remoteId.toString()),
trailing: ElevatedButton(
child: const Text('连接'),
onPressed: () => _connectToDevice(device),
),
);
},
),
);
}
}
// 设备服务界面
class DeviceScreen extends StatefulWidget {
final BluetoothDevice device;
const DeviceScreen({super.key, required this.device});
@override
State<DeviceScreen> createState() => _DeviceScreenState();
}
class _DeviceScreenState extends State<DeviceScreen> {
List<BluetoothService> _services = [];
bool _isConnecting = true;
@override
void initState() {
super.initState();
_discoverServices();
}
// 发现服务
Future<void> _discoverServices() async {
try {
List<BluetoothService> services = await widget.device.discoverServices();
setState(() {
_services = services;
_isConnecting = false;
});
} catch (e) {
print("发现服务失败: $e");
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.device.platformName),
),
body: _isConnecting
? const Center(child: CircularProgressIndicator())
: ListView.builder(
itemCount: _services.length,
itemBuilder: (context, index) {
BluetoothService service = _services[index];
return ExpansionTile(
title: Text(service.uuid.toString()),
children: service.characteristics.map((characteristic) {
return ListTile(
title: Text(characteristic.uuid.toString()),
subtitle: Text('特性: ${characteristic.properties}'),
trailing: IconButton(
icon: const Icon(Icons.edit),
onPressed: () {
// 进入特征操作界面
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => CharacteristicScreen(characteristic: characteristic),
),
);
},
),
);
}).toList(),
);
},
),
);
}
}
// 特征操作界面(可选)
class CharacteristicScreen extends StatefulWidget {
final BluetoothCharacteristic characteristic;
const CharacteristicScreen({super.key, required this.characteristic});
@override
State<CharacteristicScreen> createState() => _CharacteristicScreenState();
}
class _CharacteristicScreenState extends State<CharacteristicScreen> {
List<int>? _value;
bool _isReading = false;
bool _isWriting = false;
// 读取特征值
Future<void> _readValue() async {
setState(() {
_isReading = true;
});
try {
List<int> value = await widget.characteristic.read();
setState(() {
_value = value;
_isReading = false;
});
} catch (e) {
print("读取失败: $e");
setState(() {
_isReading = false;
});
}
}
// 写入特征值(示例:写入0x01)
Future<void> _writeValue() async {
setState(() {
_isWriting = true;
});
try {
await widget.characteristic.write([0x01]);
setState(() {
_isWriting = false;
});
} catch (e) {
print("写入失败: $e");
setState(() {
_isWriting = false;
});
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('特征操作'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text('特征值: ${_value?.map((e) => e.toRadixString(16)).join(', ') ?? '未读取'}'),
const SizedBox(height: 20),
ElevatedButton(
onPressed: _isReading ? null : _readValue,
child: _isReading
? const CircularProgressIndicator()
: const Text('读取特征值'),
),
const SizedBox(height: 20),
ElevatedButton(
onPressed: _isWriting ? null : _writeValue,
child: _isWriting
? const CircularProgressIndicator()
: const Text('写入[0x01]'),
),
],
),
),
);
}
}
```
## 代码解释
1. **权限检查**:在应用启动时检查并请求蓝牙和位置权限(Android需要位置权限来扫描BLE设备)。
2. **扫描设备**:使用`FlutterBluePlus.startScan()`开始扫描,通过监听`FlutterBluePlus.scanResults`来获取扫描结果。
3. **设备连接**:点击设备列表中的设备,通过`device.connect()`进行连接。
4. **发现服务**:连接设备后,调用`device.discoverServices()`获取设备提供的服务。
5. **服务和特征展示**:以可展开的列表展示服务及其包含的特征。
6. **特征操作**:在特征操作界面,可以读取和写入特征值。
## 运行应用
连接物理设备(Android或iOS),确保设备支持蓝牙,然后运行:
```bash
flutter run
```
## 注意事项
- 在真实设备上运行,模拟器可能不支持蓝牙。
- 确保设备蓝牙已开启。
- 不同平台的权限处理可能不同,需要仔细测试。
---
### §§相关问题§§:
1. 如何实现蓝牙设备的断线自动重连?
2. 如何监听蓝牙设备发送的通知(notify)?
3. 在Android和iOS上处理蓝牙权限有哪些不同?
4. 如何优化蓝牙扫描的能耗?
</think># 创建蓝牙连接App工程:完整指南与示例代码
下面我将详细介绍如何从零开始创建一个功能完整的蓝牙连接App工程,使用Flutter框架来实现跨平台兼容性。
## 📱 工程结构概述
```
bluetooth_connect_app/
├── lib/
│ ├── main.dart # 应用入口
│ ├── screens/
│ │ ├── home_screen.dart # 主界面
│ │ ├── scan_screen.dart # 扫描界面
│ │ └── device_screen.dart# 设备控制界面
│ ├── widgets/
│ │ └── device_card.dart # 设备卡片组件
│ └── bluetooth/
│ └── bluetooth_service.dart # 蓝牙服务封装
├── android/ # Android平台配置
├── ios/ # iOS平台配置
└── pubspec.yaml # 依赖管理
```
## 📦 依赖配置(pubspec.yaml)
```yaml
name: bluetooth_connect_app
description: A complete Bluetooth connection application
environment:
sdk: ">=3.0.0 <4.0.0"
flutter: ">=3.0.0"
dependencies:
flutter:
sdk: flutter
cupertino_icons: ^1.0.6
flutter_blue_plus: ^1.8.3 # 蓝牙核心库
permission_handler: ^11.3.1 # 权限管理
provider: ^6.0.5 # 状态管理
auto_size_text: ^3.0.0 # 自适应文本
flutter_spinkit: ^5.2.0 # 加载动画
dev_dependencies:
flutter_lints: ^3.0.0
flutter_test:
sdk: flutter
flutter:
uses-material-design: true
assets:
- assets/ # 图标资源
```
## 核心代码实现
### 1. 主入口文件 (lib/main.dart)
```dart
import 'package:flutter/material.dart';
import 'package:bluetooth_connect_app/screens/home_screen.dart';
import 'package:bluetooth_connect_app/bluetooth/bluetooth_service.dart';
import 'package:provider/provider.dart';
void main() {
runApp(
MultiProvider(
providers: [
ChangeNotifierProvider(create: (_) => BluetoothService()),
],
child: const MyApp(),
),
);
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: '蓝牙连接助手',
theme: ThemeData(
primarySwatch: Colors.blue,
appBarTheme: const AppBarTheme(
backgroundColor: Colors.white,
foregroundColor: Colors.black,
elevation: 0,
),
scaffoldBackgroundColor: Colors.white,
),
home: const HomeScreen(),
debugShowCheckedModeBanner: false,
);
}
}
```
### 2. 蓝牙服务封装 (lib/bluetooth/bluetooth_service.dart)
```dart
import 'dart:async';
import 'package:flutter/foundation.dart';
import 'package:flutter_blue_plus/flutter_blue_plus.dart';
class BluetoothService with ChangeNotifier {
// 状态变量
bool _isScanning = false;
bool _isConnected = false;
BluetoothDevice? _connectedDevice;
List<BluetoothService> _services = [];
List<BluetoothDevice> _discoveredDevices = [];
String _connectionStatus = "未连接";
StreamSubscription<List<ScanResult>>? _scanSubscription;
// 获取器
bool get isScanning => _isScanning;
bool get isConnected => _isConnected;
BluetoothDevice? get connectedDevice => _connectedDevice;
List<BluetoothDevice> get discoveredDevices => _discoveredDevices;
String get connectionStatus => _connectionStatus;
List<BluetoothService> get services => _services;
// 检查权限
Future<bool> checkPermissions() async {
try {
if (await FlutterBluePlus.isSupported == false) {
_connectionStatus = "设备不支持蓝牙";
notifyListeners();
return false;
}
// 请求权限
final statuses = await [
Permission.bluetoothScan,
Permission.bluetoothConnect,
Permission.locationWhenInUse,
].request();
if (statuses[Permission.bluetoothScan]!.isGranted &&
statuses[Permission.bluetoothConnect]!.isGranted) {
return true;
} else {
_connectionStatus = "缺少必要权限";
notifyListeners();
return false;
}
} catch (e) {
_connectionStatus = "权限错误: $e";
notifyListeners();
return false;
}
}
// 开始扫描设备
Future<void> startScan({int timeout = 15}) async {
if (_isScanning) return;
if (await checkPermissions() == false) return;
// 重置设备列表
_discoveredDevices.clear();
_isScanning = true;
_connectionStatus = "扫描中...";
notifyListeners();
try {
// 设置扫描配置
await FlutterBluePlus.startScan(
timeout: Duration(seconds: timeout),
scanMode: ScanMode.lowLatency,
);
// 监听扫描结果
_scanSubscription = FlutterBluePlus.scanResults.listen((results) {
for (ScanResult result in results) {
final device = result.device;
// 过滤掉无效设备
if (device.platformName.isNotEmpty &&
!_discoveredDevices.contains(device)) {
_discoveredDevices.add(device);
notifyListeners();
}
}
}, onError: (e) {
_connectionStatus = "扫描错误: $e";
_isScanning = false;
notifyListeners();
});
// 超时停止扫描
Timer(Duration(seconds: timeout), () {
if (_isScanning) stopScan();
});
} catch (e) {
_connectionStatus = "扫描失败: $e";
_isScanning = false;
notifyListeners();
}
}
// 停止扫描
void stopScan() {
if (!_isScanning) return;
_scanSubscription?.cancel();
FlutterBluePlus.stopScan();
_isScanning = false;
_connectionStatus = "扫描完成";
notifyListeners();
}
// 连接设备
Future<void> connectToDevice(BluetoothDevice device) async {
if (_isConnected) return;
try {
_connectionStatus = "正在连接...";
notifyListeners();
await device.connect(autoConnect: false, timeout: Duration(seconds: 10));
_connectedDevice = device;
_isConnected = true;
_connectionStatus = "已连接";
// 发现服务
_services = await device.discoverServices();
notifyListeners();
} catch (e) {
_connectionStatus = "连接失败: $e";
notifyListeners();
}
}
// 断开连接
Future<void> disconnectDevice() async {
if (!_isConnected || _connectedDevice == null) return;
try {
await _connectedDevice!.disconnect();
_isConnected = false;
_connectedDevice = null;
_services.clear();
_connectionStatus = "已断开";
notifyListeners();
} catch (e) {
_connectionStatus = "断开失败: $e";
notifyListeners();
}
}
// 写入特征值
Future<void> writeCharacteristic(BluetoothCharacteristic characteristic, List<int> value) async {
try {
await characteristic.write(value);
} catch (e) {
_connectionStatus = "写入失败: $e";
notifyListeners();
}
}
// 读取特征值
Future<List<int>> readCharacteristic(BluetoothCharacteristic characteristic) async {
try {
return await characteristic.read();
} catch (e) {
_connectionStatus = "读取失败: $e";
notifyListeners();
return [];
}
}
@override
void dispose() {
_scanSubscription?.cancel();
disconnectDevice();
super.dispose();
}
}
```
### 3. 主界面 (lib/screens/home_screen.dart)
```dart
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:bluetooth_connect_app/bluetooth/bluetooth_service.dart';
import 'package:bluetooth_connect_app/screens/scan_screen.dart';
import 'package:bluetooth_connect_app/widgets/device_card.dart';
class HomeScreen extends StatelessWidget {
const HomeScreen({super.key});
@override
Widget build(BuildContext context) {
final bluetoothService = Provider.of<BluetoothService>(context);
return Scaffold(
appBar: AppBar(
title: const Text('蓝牙连接助手'),
actions: [
IconButton(
icon: Icon(bluetoothService.isScanning ? Icons.stop : Icons.search),
onPressed: () {
if (bluetoothService.isScanning) {
bluetoothService.stopScan();
} else {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => const ScanScreen()),
);
}
},
),
],
),
body: Column(
children: [
// 状态显示
Container(
padding: const EdgeInsets.symmetric(vertical: 16, horizontal: 20),
color: Colors.grey[50],
child: Row(
children: [
Icon(
bluetoothService.isConnected ? Icons.bluetooth_connected : Icons.bluetooth_disabled,
color: bluetoothService.isConnected ? Colors.blue : Colors.grey,
),
const SizedBox(width: 10),
Expanded(
child: Text(
bluetoothService.connectionStatus,
style: const TextStyle(fontSize: 16),
),
),
],
),
),
// 已连接设备
if (bluetoothService.isConnected)
Padding(
padding: const EdgeInsets.all(16.0),
child: DeviceCard(device: bluetoothService.connectedDevice!),
),
// 设备操作区域
if (bluetoothService.isConnected)
Expanded(
child: ListView.builder(
itemCount: bluetoothService.services.length,
itemBuilder: (context, index) {
final service = bluetoothService.services[index];
return ExpansionTile(
title: Text(service.uuid.toString()),
subtitle: Text('服务'),
children: service.characteristics.map((characteristic) {
return ListTile(
title: Text(characteristic.uuid.toString()),
subtitle: Text(
'特性: ${characteristic.properties.toString().replaceAll("BluetoothCharacteristicProperty.", "")}'
),
trailing: Row(
mainAxisSize: MainAxisSize.min,
children: [
if (characteristic.properties.read)
IconButton(
icon: const Icon(Icons.download),
onPressed: () async {
final value = await bluetoothService.readCharacteristic(characteristic);
// 处理读取的值
},
),
if (characteristic.properties.write)
IconButton(
icon: const Icon(Icons.upload),
onPressed: () {
// 写入示例值
bluetoothService.writeCharacteristic(characteristic, [0x01]);
},
),
],
),
);
}).toList(),
);
},
),
),
// 未连接时的提示
if (!bluetoothService.isConnected)
const Expanded(
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.bluetooth, size: 80, color: Colors.grey),
SizedBox(height: 20),
Text('未连接任何设备', style: TextStyle(fontSize: 18)),
Text('请点击右上角扫描按钮查找设备', style: TextStyle(color: Colors.grey)),
],
),
),
),
],
),
// 断开连接按钮
if (bluetoothService.isConnected)
Padding(
padding: const EdgeInsets.all(16.0),
child: ElevatedButton.icon(
icon: const Icon(Icons.link_off),
label: const Text('断开连接'),
style: ElevatedButton.styleFrom(
backgroundColor: Colors.red,
foregroundColor: Colors.white,
minimumSize: const Size(double.infinity, 50),
),
onPressed: bluetoothService.disconnectDevice,
),
),
);
}
}
```
### 4. 扫描界面 (lib/screens/scan_screen.dart)
```dart
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:bluetooth_connect_app/bluetooth/bluetooth_service.dart';
import 'package:bluetooth_connect_app/widgets/device_card.dart';
class ScanScreen extends StatefulWidget {
const ScanScreen({super.key});
@override
State<ScanScreen> createState() => _ScanScreenState();
}
class _ScanScreenState extends State<ScanScreen> {
@override
void initState() {
super.initState();
// 进入页面后开始扫描
WidgetsBinding.instance.addPostFrameCallback((_) {
Provider.of<BluetoothService>(context, listen: false).startScan();
});
}
@override
void dispose() {
// 离开页面时停止扫描
Provider.of<BluetoothService>(context, listen: false).stopScan();
super.dispose();
}
@override
Widget build(BuildContext context) {
final bluetoothService = Provider.of<BluetoothService>(context);
return Scaffold(
appBar: AppBar(
title: const Text('扫描蓝牙设备'),
leading: IconButton(
icon: const Icon(Icons.arrow_back),
onPressed: () => Navigator.pop(context),
),
actions: [
IconButton(
icon: Icon(bluetoothService.isScanning ? Icons.stop : Icons.refresh),
onPressed: () {
if (bluetoothService.isScanning) {
bluetoothService.stopScan();
} else {
bluetoothService.startScan();
}
},
),
],
),
body: Column(
children: [
// 扫描状态
Container(
padding: const EdgeInsets.all(16),
color: Colors.grey[50],
child: Row(
children: [
if (bluetoothService.isScanning)
const SizedBox(
width: 24,
height: 24,
child: CircularProgressIndicator(strokeWidth: 2),
)
else
const Icon(Icons.info, color: Colors.blue),
const SizedBox(width: 10),
Text(
bluetoothService.isScanning
? "正在扫描附近设备..."
: "扫描完成,发现${bluetoothService.discoveredDevices.length}个设备",
),
],
),
),
// 设备列表
Expanded(
child: bluetoothService.discoveredDevices.isEmpty
? const Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.devices_other, size: 80, color: Colors.grey),
SizedBox(height: 20),
Text('未发现任何设备', style: TextStyle(fontSize: 18)),
Text('请确保蓝牙开启且设备处于可发现状态', style: TextStyle(color: Colors.grey)),
],
),
)
: ListView.builder(
itemCount: bluetoothService.discoveredDevices.length,
itemBuilder: (context, index) {
final device = bluetoothService.discoveredDevices[index];
return DeviceCard(
device: device,
onTap: () {
bluetoothService.connectToDevice(device);
Navigator.pop(context);
},
);
},
),
),
],
),
);
}
}
```
### 5. 设备卡片组件 (lib/widgets/device_card.dart)
```dart
import 'package:flutter/material.dart';
import 'package:flutter_blue_plus/flutter_blue_plus.dart';
class DeviceCard extends StatelessWidget {
final BluetoothDevice device;
final VoidCallback? onTap;
const DeviceCard({
super.key,
required this.device,
this.onTap,
});
@override
Widget build(BuildContext context) {
return Card(
margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
child: ListTile(
leading: const Icon(Icons.devices, size: 36),
title: Text(
device.platformName,
style: const TextStyle(fontWeight: FontWeight.bold),
overflow: TextOverflow.ellipsis,
),
subtitle: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('MAC: ${device.remoteId}'),
Text('信号强度: ${device.rssi} dBm'),
],
),
trailing: onTap != null
? IconButton(
icon: const Icon(Icons.link),
onPressed: onTap,
)
: const Icon(Icons.check_circle, color: Colors.green),
onTap: onTap,
),
);
}
}
```
## 🔧 平台特定配置
### Android配置 (android/app/src/main/AndroidManifest.xml)
```xml
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.BLUETOOTH"/>
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
<!-- Android 12+ -->
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<application
android:label="bluetooth_connect_app"
android:name="${applicationName}"
android:icon="@mipmap/ic_launcher">
<activity
android:name=".MainActivity"
android:exported="true"
android:launchMode="singleTop"
android:theme="@style/LaunchTheme"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layout