I'm using flutter_reactive_ble and flutter_blue_plus in case of both libraries, when the code is built on android 11, the bluetooth module works quite fine. Incase of Andorid 11+ it doesn't show some of the scanned devices.
manifest
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<!-- permissions handling -->
<!-- Bluetooth -->
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.BLUETOOTH_ADVERTISE" />
<!-- Notifications -->
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
<!-- Camera -->
<uses-permission android:name="android.permission.CAMERA"/>
<!-- Location -->
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
<application
android:label="melo"
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|layoutDirection|fontScale|screenLayout|density|uiMode"
android:hardwareAccelerated="true"
android:windowSoftInputMode="adjustResize">
<!-- Specifies an Android theme to apply to this Activity as soon as
the Android process has started. This theme is visible to the user
while the Flutter UI initializes. After that, this theme continues
to determine the Window background behind the Flutter UI. -->
<meta-data
android:name="io.flutter.embedding.android.NormalTheme"
android:resource="@style/NormalTheme"
/>
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<!-- Don't delete the meta-data below.
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
<meta-data
android:name="flutterEmbedding"
android:value="2" />
</application>
scanning code(working fine over android 11, and missing some devices android 11 onwards):
import 'package:animated_custom_dropdown/custom_dropdown.dart';
import 'package:flutter/material.dart';
import 'package:flutter_reactive_ble/flutter_reactive_ble.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:melo/ble/ble_scanner.dart';
import 'package:melo/providers/new_instrument_provider.dart';
import 'package:melo/screens/instrument_enrollment/primary_info.dart';
import 'package:melo/utils/colors.dart';
import 'package:melo/widgets/add_instrument/bluetooth_scan_item.dart';
import 'package:melo/widgets/add_instrument/steps.dart';
import 'package:melo/widgets/others/theme_button.dart';
class SelectInstrumentScreen extends ConsumerStatefulWidget {
const SelectInstrumentScreen({super.key, required this.collectionName});
final String collectionName;
@override
ConsumerState<SelectInstrumentScreen> createState() =>
_SelectInstrumentScreenState();
}
class _SelectInstrumentScreenState
extends ConsumerState<SelectInstrumentScreen> {
final BleScanner bleScanner = BleScanner(
ble: FlutterReactiveBle(),
logMessage: (message) {},
);
@override
void initState() {
super.initState();
bleScanner.startScan([]);
}
@override
Widget build(BuildContext context) {
ref.watch(newInstrumentProvider);
bool areValuesSelected = ref.read(newInstrumentProvider)['type'] != null &&
ref.read(newInstrumentProvider)['macAddress'] != null;
return Scaffold(
backgroundColor: backgroundColor,
appBar: AppBar(
backgroundColor: Colors.white,
leadingWidth: 0,
foregroundColor: Colors.transparent,
toolbarHeight: 70,
title: Container(
padding: const EdgeInsets.all(5),
child: const StepsIndicator(step: 1),
),
),
body: Column(
children: [
Container(
padding: const EdgeInsets.symmetric(horizontal: 20),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const SizedBox(height: 5),
const Text(
'Select Sensor Device',
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 15,
),
),
Text(
'Select the sensor device that you want to connect from the available devices.',
style: TextStyle(
color: secondaryColor,
fontSize: 13,
),
),
// select instrument from a list of options
const SizedBox(height: 10),
const Text(
'Choose Instrument',
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 14,
),
),
const SizedBox(height: 5),
CustomDropdown<String>(
closedHeaderPadding: const EdgeInsets.only(
top: 7, bottom: 8, left: 15, right: 10),
decoration: CustomDropdownDecoration(
closedBorderRadius: BorderRadius.circular(100),
closedBorder: Border.all(
color: const Color.fromRGBO(220, 220, 220, 1),
),
),
overlayHeight: MediaQuery.of(context).size.height * 0.5,
hintText: 'Choose Instrument',
items: _listInstruments,
initialItem: ref.read(newInstrumentProvider)['type'],
onChanged: (value) {
ref
.read(newInstrumentProvider.notifier)
.update('type', value);
},
),
// bluetooth scanned devices
const SizedBox(height: 10),
const Text(
'Available Bluetooth Devices',
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 14,
),
),
const SizedBox(height: 5),
// reactive ble devices
],
),
),
Expanded(
child: StreamBuilder<BleScannerState>(
stream: bleScanner.state,
builder: (context, snapshot) {
if (snapshot.hasData) {
final discoveredDevices = snapshot.data!.discoveredDevices;
// sorting: nearest device first
discoveredDevices.sort((a, b) => b.rssi.compareTo(a.rssi));
return ListView.builder(
itemCount: discoveredDevices.length,
itemBuilder: (context, index) {
final device = discoveredDevices[index];
return Material(
child: InkWell(
onTap: () {
ref
.read(newInstrumentProvider.notifier)
.update('macAddress', device.id);
ref.read(newInstrumentProvider.notifier).update(
'name',
device.name.isNotEmpty
? device.name
: 'Unknown Name');
},
child: BluetoothScanItem(device: device),
),
);
},
);
} else {
return const Text('nothing so far');
}
},
),
),
Container(
height: 60,
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
child: Row(
children: [
Expanded(
flex: 2,
child: ThemeButton(
onTap: () {
Navigator.pop(context);
},
name: 'Back',
isSimple: true,
),
),
const SizedBox(width: 10),
Expanded(
flex: 3,
child: ThemeButton(
onTap: () {
if (areValuesSelected) {
Navigator.push(context,
MaterialPageRoute(builder: (context) {
return PrimaryInfoScreen(
collectionName: widget.collectionName);
}));
}
},
name: 'Next',
isDisabled: !areValuesSelected,
),
),
],
),
),
],
),
);
}
}
const List<String> _listInstruments = [
'Acoustic',
'Bass',
'Bowed',
'Brass',
'Drum',
'Electric',
'Mandolin',
'Piano',
'Reed',
'Other',
];
Since the release of Android 12, new Bluetooth permissions have been introduced. According to the documentation, BLUETOOTH_SCAN, BLUETOOTH_CONNECT, and BLUETOOTH_ADVERTISE are considered runtime permissions.
This means that if your application is targeting devices running Android 12 or higher, you must handle these permissions yourself and request them explicitly from the user.
For example, when you intend to scan for BLE devices, you must request the BLUETOOTH_SCAN permission. Note that this permission will be presented to the user as "Nearby devices" permission.
Failure to request and obtain these permissions may result in your BleScanner being unable to detect any nearby devices.
Note that for devices lower than Android 12, you still need to ask for runtime location permission.