You're building an IoT device and need to decide how it communicates with an Android application. The choice between Bluetooth Low Energy (BLE) and Wi-Fi isn't trivial; it dictates your device's power consumption, data throughput, range, and overall user experience. This decision, if made incorrectly, can lead to frustrating battery life, slow data transfers, or an unreliable connection, directly impacting your product's success. This article will equip you with the knowledge to make an informed decision by dissecting the core differences, Android implementation, and best practices for both protocols.
Core Concepts: Understanding BLE and Wi-Fi
Before diving into Android specifics, let's establish a foundational understanding of BLE and Wi-Fi from an IoT perspective. Each protocol offers distinct advantages and disadvantages that make them suitable for different scenarios.
Bluetooth Low Energy (BLE)
BLE is a wireless personal area network technology designed for very low power consumption, making it ideal for battery-powered devices that need to operate for months or even years. It operates in the 2.4 GHz ISM band.
- Topology: BLE primarily uses a star topology where a central device connects to multiple peripheral devices. It also supports mesh networking (BLE Mesh) for extending range and connecting many devices, though this adds complexity.
- Roles:
- Central: Scans for and connects to peripherals (e.g., an Android phone).
- Peripheral: Advertises its presence and accepts connections from centrals (e.g., an IoT sensor).
- GATT (Generic Attribute Profile): Once connected, BLE devices communicate using GATT.
- Services: Collections of characteristics and relationships to other services. For example, a "Heart Rate Service" might contain a "Heart Rate Measurement Characteristic."
- Characteristics: Data points, including a value, properties (read, write, notify), and descriptors (metadata).
- Advertisements: Peripherals broadcast small packets of data (advertisements) to announce their presence without establishing a connection. Centrals can scan for these advertisements.
- Data Rate: Typically low, suitable for small, periodic data transfers (e.g., sensor readings, control commands).
- Range: Short, usually up to 10-30 meters indoors.
Wi-Fi (IEEE 802.11)
Wi-Fi is a wireless local area network technology known for its high data throughput and longer range. It typically operates in the 2.4 GHz and 5 GHz bands.
- Topology: Most commonly uses an infrastructure mode, where devices connect to a central Access Point (AP) which is part of a larger network (e.g., your home router). It also supports Ad-hoc mode (device-to-device) and Wi-Fi Direct, though these are less common for simple IoT device-to-cloud scenarios.
- IP-based: Wi-Fi devices are full-fledged network citizens, receiving an IP address and communicating over standard IP protocols (TCP/UDP), enabling direct internet access.
- Data Rate: High, suitable for streaming data, large file transfers, and frequent updates.
- Range: Medium to long, typically up to 50-100 meters indoors, dependent on environment and AP strength.
- Power Consumption: Generally higher than BLE, requiring a stable power source or larger batteries.
Comparison Table: BLE vs. Wi-Fi for IoT
| Feature | Bluetooth Low Energy (BLE) | Wi-Fi (802.11) |
|---|---|---|
| Power | Very Low | High |
| Battery Life | Months to Years (coin cell) | Hours to Days (rechargeable) |
| Data Rate | Low (tens of kbps) | High (tens to hundreds of Mbps) |
| Range | Short (10-30m indoors) | Medium-Long (50-100m indoors) |
| Network | Star, Mesh, Connection-oriented (GATT) or connectionless (Advertisements) | Infrastructure (AP), IP-based |
| Complexity | Simpler for device setup, more focused on profiles | More complex initial setup, full network stack |
| Cost | Lower module cost | Higher module cost (generally) |
| Use Cases | Sensors, wearables, beacons, small control | Video streaming, complex remote control, cloud gateway |
When to choose which?
- Choose BLE when: your device is battery-powered, sends small amounts of data infrequently, requires direct proximity connection, or needs to form a local mesh network.
- Choose Wi-Fi when: your device has a constant power source, needs to send large amounts of data, requires direct internet access, or needs to integrate seamlessly into existing home/office networks.
Implementation on Android
Android provides comprehensive APIs for interacting with both BLE and Wi-Fi. Understanding the necessary permissions and API calls is crucial for a robust IoT application.
BLE Implementation on Android
Android's BluetoothAdapter and BluetoothLeScanner are your primary interfaces for BLE interactions.
Required Permissions:
For Android 12 (API level 31) and higher:
-
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" android:usesPermissionFlags="neverForLocation" />(for scanning) -
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />(for connecting) -
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />(if you do need device location for BLE scans; often not necessary for simple IoT and can be avoided withneverForLocationflag forBLUETOOTH_SCAN).
For Android 10 (API level 29) / 11 (API level 30):
-
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" /> -
<uses-permission android:name="android.permission.BLUETOOTH" android:maxSdkVersion="30" /> -
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" android:maxSdkVersion="30" />
For Android 9 (API level 28) and lower:
-
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />(orACCESS_FINE_LOCATION) -
<uses-permission android:name="android.permission.BLUETOOTH" /> -
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
Runtime Permissions:
Since ACCESS_FINE_LOCATION, BLUETOOTH_SCAN, and BLUETOOTH_CONNECT are dangerous permissions, you must request them from the user at runtime.
Common Gotchas for BLE:
- Location Services: On Android 9, 10, and 11, the system uses location services for BLE scanning results to avoid exposing user location without explicit permission. This means users must have location services enabled on their device for your app to find BLE devices. For Android 12+,
BLUETOOTH_SCANwithneverForLocationhelps mitigate this if you don't need location context. - Bluetooth Adapter State: Always check if Bluetooth is enabled on the device. If not, prompt the user to enable it using
ACTION_REQUEST_ENABLE. - Background Scanning: Android aggressively optimizes background processes. Long-running background BLE scans are restricted. Implement duty-cycled scanning or foreground services if continuous background scanning is critical.
-
ScanCallbackErrors: HandleonScanFailedcorrectly. Common errors includeSCAN_FAILED_ALREADY_STARTED,SCAN_FAILED_APPLICATION_REGISTRATION_FAILED, andSCAN_FAILED_FEATURE_UNSUPPORTED. - GATT Connection Lifecycles: Properly close GATT connections (
gatt.close()) when no longer needed to free resources and prevent connection leaks.
Wi-Fi Implementation on Android
Android's WifiManager is the primary class for managing Wi-Fi connectivity.
Required Permissions:
-
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />(to check Wi-Fi state) -
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />(to change Wi-Fi state, e.g., enable/disable) -
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />(for Wi-Fi scan results) -
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />(for Android 10+ to manage network requests) -
<uses-permission android:name="android.permission.INTERNET" />(if your app needs to access the internet via Wi-Fi)
Runtime Permissions:
ACCESS_FINE_LOCATION and CHANGE_NETWORK_STATE (implicitly for network suggestions) are dangerous permissions and must be requested at runtime.
Common Gotchas for Wi-Fi:
- Location Services: Similar to BLE, for your app to get Wi-Fi scan results (
WifiManager.getScanResults()), location services must be enabled on the device for Android 9+. - Network Configuration: Connecting to Wi-Fi programmatically has become more restricted over Android versions for security and privacy.
- Android 10+ (API 29+): Use
WifiNetworkSpecifierwithConnectivityManagerfor secure provisioning. This allows the user to approve a network suggestion. Directly manipulatingWifiConfigurationis largely deprecated or requires system app privileges. - Older Android versions:
WifiManager.addNetwork()andWifiManager.enableNetwork()were used. This method is less secure and less user-friendly due to lack of explicit user approval.
- Android 10+ (API 29+): Use
- User Approval: Modern Android versions prioritize user control over network connections. Expect the user to confirm network connections or provisioning requests. Your app cannot silently connect to a Wi-Fi network with credentials.
- Network Disruption: When your app tries to connect to a specific Wi-Fi network, it might disconnect the user from their current network. Handle this gracefully and inform the user.
- Background Wi-Fi Scans: Like BLE, frequent background Wi-Fi scans can consume significant power. Use
WifiManager.startScan()sparingly.
Code Examples
Here are two Kotlin snippets demonstrating fundamental interactions: one for BLE scanning and one for Wi-Fi scanning.
1. BLE Device Scanning (Android 12+)
This example demonstrates how to start and stop a BLE scan, ensuring all necessary permissions are handled for Android 12 (API 31) and higher.
import android.Manifest
import android.bluetooth.BluetoothAdapter
import android.bluetooth.BluetoothManager
import android.bluetooth.le.ScanCallback
import android.bluetooth.le.ScanResult
import android.bluetooth.le.ScanSettings
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.os.Build
import android.os.Bundle
import android.util.Log
import android.widget.Toast
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat
class BleScanActivity : AppCompatActivity() {
private val TAG = "BleScanActivity"
private var bluetoothAdapter: BluetoothAdapter? = null
private val bleScanner by lazy { bluetoothAdapter?.bluetoothLeScanner }
private val REQUIRED_BLE_PERMISSIONS = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
// Android 12 (API 31) and higher requires these
arrayOf(
Manifest.permission.BLUETOOTH_SCAN,
Manifest.permission.BLUETOOTH_CONNECT,
Manifest.permission.ACCESS_FINE_LOCATION // Still needed for some older API compatibility or if location is specifically desired
)
} else {
// Android 11 (API 30) and lower requires these for scanning nearby devices
arrayOf(Manifest.permission.ACCESS_FINE_LOCATION)
}
// Handles the permission request result
private val requestPermissionLauncher =
registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) { permissions ->
val granted = permissions.entries.all { it.value } // Check if all permissions were granted
if (granted) {
Log.d(TAG, "All required BLE permissions granted.")
checkBluetoothAvailability() // Proceed if permissions are granted
} else {
Log.e(TAG, "Not all BLE permissions granted.")
Toast.makeText(this, "BLE permissions are required for scanning.", Toast.LENGTH_LONG).show()
}
}
// Handles the result of enabling Bluetooth
private val enableBluetoothLauncher =
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
if (result.resultCode == RESULT_OK) {
Log.d(TAG, "Bluetooth enabled by user.")
startBleScan() // Start scan if Bluetooth is enabled
} else {
Log.e(TAG, "Bluetooth not enabled by user.")
Toast.makeText(this, "Bluetooth must be enabled to scan for devices.", Toast.LENGTH_LONG).show()
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main) // Assume a simple layout with buttons to start/stop scan
val bluetoothManager = getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager
bluetoothAdapter = bluetoothManager.adapter
// Initiate permission request
requestPermissionLauncher.launch(REQUIRED_BLE_PERMISSIONS)
}
private fun checkBluetoothAvailability() {
if (bluetoothAdapter == null || !bluetoothAdapter!!.isEnabled) {
// Bluetooth is not enabled, prompt the user
val enableBtIntent = Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE)
enableBluetoothLauncher.launch(enableBtIntent)
} else {
// Bluetooth is enabled, proceed to scan
startBleScan()
}
}
private val bleScanCallback: ScanCallback = object : ScanCallback() {
override fun onScanResult(callbackType: Int, result: ScanResult) {
super.onScanResult(callbackType, result)
// Process the discovered BLE device here
val device = result.device
Log.d(TAG, "Found BLE device: ${device.name ?: "Unknown"} - ${device.address}")
// You might want to add this to a list and display in UI
}
override fun onBatchScanResults(results: List<ScanResult>) {
super.onBatchScanResults(results)
// This is called if batching is enabled in ScanSettings
for (result in results) {
val device = result.device
Log.d(TAG, "Batch found BLE device: ${device.name ?: "Unknown"} - ${device.address}")
}
}
override fun onScanFailed(errorCode: Int) {
super.onScanFailed(errorCode)
// Handle scan failure
Log.e(TAG, "BLE Scan Failed with error code: $errorCode")
Toast.makeText(this@BleScanActivity, "BLE Scan Failed: $errorCode", Toast.LENGTH_LONG).show()
}
}
private fun startBleScan() {
// Check if permissions are still granted before starting scan
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
if (ContextCompat.checkSelfPermission(this, Manifest.permission.BLUETOOTH_SCAN) != PackageManager.PERMISSION_GRANTED) {
Log.w(TAG, "BLUETOOTH_SCAN permission not granted. Cannot start scan.")
return
}
} else {
if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
Log.w(TAG, "ACCESS_FINE_LOCATION permission not granted. Cannot start scan.")
return
}
}
bleScanner?.let {
val settings = ScanSettings.Builder()
.setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY) // Use low latency for faster detection, but higher power
.build()
it.startScan(null, settings, bleScanCallback) // Start scan with no filters, specific settings, and our callback
Log.d(TAG, "BLE Scan started.")
Toast.makeText(this, "BLE Scan Started", Toast.LENGTH_SHORT).show()
} ?: run {
Log.e(TAG, "BluetoothLeScanner is null. Is Bluetooth enabled?")
}
}
private fun stopBleScan() {
// Ensure permissions are still granted when stopping to avoid SecurityException
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
if (ContextCompat.checkSelfPermission(this, Manifest.permission.BLUETOOTH_SCAN) != PackageManager.PERMISSION_GRANTED) {
Log.w(TAG, "BLUETOOTH_SCAN permission not granted. Cannot stop scan.")
return
}
} else {
if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
Log.w(TAG, "ACCESS_FINE_LOCATION permission not granted. Cannot stop scan.")
return
}
}
bleScanner?.let {
it.stopScan(bleScanCallback) // Stop the scan using the same callback instance
Log.d(TAG, "BLE Scan stopped.")
Toast.makeText(this, "BLE Scan Stopped", Toast.LENGTH_SHORT).show()
}
}
override fun onPause() {
super.onPause()
stopBleScan() // Stop scanning when activity pauses to save battery
}
}
2. Wi-Fi Network Scanning (Modern Android)
This example shows how to initiate a Wi-Fi scan and retrieve available Wi-Fi networks using WifiManager. This is appropriate for an IoT app that might list available Wi-Fi networks for the user to select for provisioning a device.
import android.Manifest
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.content.pm.PackageManager
import android.net.wifi.ScanResult
import android.net.wifi.WifiManager
import android.os.Build
import android.os.Bundle
import android.util.Log
import android.widget.Toast
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat
class WifiScanActivity : AppCompatActivity() {
private val TAG = "WifiScanActivity"
private lateinit var wifiManager: WifiManager
// Permission required for getting Wi-Fi scan results on modern Android
private val REQUIRED_WIFI_PERMISSION = Manifest.permission.ACCESS_FINE_LOCATION
// Handles the permission request result
private val requestPermissionLauncher =
registerForActivityResult(ActivityResultContracts.RequestPermission()) { isGranted ->
if (isGranted) {
Log.d(TAG, "ACCESS_FINE_LOCATION permission granted for Wi-Fi scan.")
startWifiScan() // Proceed to scan if permission is granted
} else {
Log.e(TAG, "ACCESS_FINE_LOCATION permission not granted for Wi-Fi scan.")
Toast.makeText(this, "Location permission is required for Wi-Fi scanning.", Toast.LENGTH_LONG).show()
}
}
// Broadcast receiver to get scan results
private val wifiScanReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
val success = intent.getBooleanExtra(WifiManager.EXTRA_RESULTS_UPDATED, false)
if (success) {
scanSuccess()
} else {
scanFailure()
}
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main) // Assume a simple layout
wifiManager = applicationContext.getSystemService(Context.WIFI_SERVICE) as WifiManager
// Register the broadcast receiver for scan results
val intentFilter = IntentFilter()
intentFilter.addAction(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION)
registerReceiver(wifiScanReceiver, intentFilter)
// Request location permission first, which is needed for Wi-Fi scans
if (ContextCompat.checkSelfPermission(this, REQUIRED_WIFI_PERMISSION) == PackageManager.PERMISSION_GRANTED) {
startWifiScan() // Permission already granted, start scan
} else {
requestPermissionLauncher.launch(REQUIRED_WIFI_PERMISSION) // Request permission
}
}
private fun startWifiScan() {
if (!wifiManager.isWifiEnabled) {
// Prompt user to enable Wi-Fi if it's not
Toast.makeText(this, "Wi-Fi is disabled. Please enable it.", Toast.LENGTH_LONG).show()
Log.w(TAG, "Wi-Fi is disabled.")
return
}
// Check again for location permission just before starting scan
if (ContextCompat.checkSelfPermission(this, REQUIRED_WIFI_PERMISSION) == PackageManager.PERMISSION_GRANTED) {
val success = wifiManager.startScan() // Initiate the scan
if (!success) {
scanFailure()
} else {
Log.d(TAG, "Wi-Fi scan initiated.")
Toast.makeText(this, "Wi-Fi Scan Initiated...", Toast.LENGTH_SHORT).show()
}
} else {
Log.w(TAG, "Location permission not granted. Cannot start Wi-Fi scan.")
Toast.makeText(this, "Location permission is required to scan Wi-Fi networks.", Toast.LENGTH_LONG).show()
}
}
private fun scanSuccess() {
val results: List<ScanResult> = wifiManager.scanResults // Get the list of scan results
Log.d(TAG, "Wi-Fi scan successful. Found ${results.size} networks.")
for (result in results) {
Log.d(TAG, "SSID: ${result.SSID}, BSSID: ${result.BSSID}, Level: ${result.level} dBm")
// You can filter results, display them in a list, etc.
}
Toast.makeText(this, "Wi-Fi Scan Complete. Found ${results.size} networks.", Toast.LENGTH_SHORT).show()
}
private fun scanFailure() {
// Handle scan failure
Log.e(TAG, "Wi-Fi scan failed.")
Toast.makeText(this, "Wi-Fi Scan Failed.", Toast.LENGTH_LONG).show()
}
override fun onDestroy() {
super.onDestroy()
unregisterReceiver(wifiScanReceiver) // Unregister receiver to prevent memory leaks
}
}
Best Practices
Choosing and implementing the right protocol requires adherence to best practices to ensure reliability, security, and a positive user experience.
BLE Best Practices
-
Pitfall: Excessive Battery Drain from Scanning. Constant or very frequent BLE scanning in
SCAN_MODE_LOW_LATENCYconsumes significant battery, both on the Android device and potential nearby advertising peripherals.- Fix: Implement duty-cycled scanning. Scan for a short period (e.g., 5-10 seconds) and then pause for a longer period (e.g., 30-60 seconds). Use
ScanSettings.Builder().setScanMode(ScanSettings.SCAN_MODE_LOW_POWER)when not actively searching for a connection. LeverageScanFilterto only discover devices with specific services or names, reducing irrelevant scan results processing.
- Fix: Implement duty-cycled scanning. Scan for a short period (e.g., 5-10 seconds) and then pause for a longer period (e.g., 30-60 seconds). Use
-
Pitfall: Unreliable GATT Connections. BLE connections can be unstable, especially in crowded RF environments or when devices move out of range, leading to dropped connections or failed operations.
- Fix: Implement robust reconnection logic with exponential back-off. Always handle
onConnectionStateChangeto detect disconnections. When a disconnection occurs, wait a short, increasing interval before attempting to reconnect. Before attempting a GATT operation (read, write, notify), always check if theBluetoothGattobject is still connected usingBluetoothManager.getConnectionState(device, BluetoothProfile.GATT). Ensure you callgatt.disconnect()andgatt.close()when a connection is no longer needed or if reconnection attempts fail persistently.
- Fix: Implement robust reconnection logic with exponential back-off. Always handle
-
Pitfall: Android Permission Fragmentation and Runtime Issues. Dealing with
ACCESS_FINE_LOCATION,BLUETOOTH_SCAN,BLUETOOTH_CONNECTacross different Android versions (maxSdkVersionand conditional checks) can be complex and lead to runtime crashes if not handled correctly.- Fix: Always target the latest stable API level and use the AndroidX Activity Result APIs (e.g.,
ActivityResultContracts.RequestMultiplePermissions) for requesting runtime permissions. Encapsulate permission checks and requests in a helper function or class. Explicitly checkBuild.VERSION.SDK_INTto apply version-specific permission logic for older devices while preparing for newer ones. Thoroughly test your permission flows on multiple Android versions and devices.
- Fix: Always target the latest stable API level and use the AndroidX Activity Result APIs (e.g.,
Wi-Fi Best Practices
-
Pitfall: High Power Consumption on Battery-Powered Devices. While Wi-Fi is generally for higher-power devices, even they can drain batteries quickly if not optimized, especially during frequent scanning or continuous network activity.
- Fix: For battery-powered Wi-Fi IoT devices, ensure the device firmware implements power-saving modes (e.g., Wi-Fi
DTIMintervals if supported by the AP, deep sleep). On the Android side, minimize unnecessary Wi-Fi scans; only initiate a scan when needed (e.g., during initial device setup) and then rely on network status broadcasts rather than polling.
- Fix: For battery-powered Wi-Fi IoT devices, ensure the device firmware implements power-saving modes (e.g., Wi-Fi
-
Pitfall: Complex or Insecure Wi-Fi Provisioning for Users. Asking users to manually enter Wi-Fi credentials for an IoT device is cumbersome, and connecting to unsecured "setup" Wi-Fi networks can pose security risks.
- Fix: Utilize modern Android APIs like
WifiNetworkSpecifier(Android 10+) orNetworkSuggestionto guide the user through secure Wi-Fi provisioning. These APIs allow the app to suggest a Wi-Fi network without directly exposing credentials, relying on user approval. Alternatively, consider using BLE for initial Wi-Fi provisioning (often called "BLE Soft AP Provisioning"), where the Android app uses BLE to send Wi-Fi credentials securely to the IoT device, which then connects to the Wi-Fi network.
- Fix: Utilize modern Android APIs like
-
Pitfall: Security Vulnerabilities in Data Transmission. Once an IoT device is on Wi-Fi, it's part of a larger IP network, making it potentially vulnerable to various network attacks if data is not secured.
- Fix: Always ensure that data exchanged between your Android app, the IoT device, and any cloud services uses robust encryption. For device-to-cloud communication, enforce TLS/SSL (HTTPS). For direct device-to-app communication, use secure protocols built on TCP/UDP (e.g., MQTT with TLS, CoAP with DTLS). Avoid transmitting sensitive information over unencrypted channels. Validate certificates and implement mutual authentication where appropriate.
Conclusion
Choosing between BLE and Wi-Fi for your IoT project hinges on a clear understanding of your device's power constraints, data requirements, and desired user experience. BLE excels in low-power, short-range, small-data scenarios, while Wi-Fi provides high-bandwidth, longer-range connectivity with direct internet access. By meticulously handling Android permissions, managing device resources efficiently, and prioritizing secure communication, you can build robust and reliable IoT applications. Your next step should be to use a "BLE Advertiser app" to simulate a peripheral and build a simple Android app that scans for it, allowing you to directly experiment with the nuances of BLE scanning and permission handling.
Top comments (0)