DEV Community

Ble Advertiser
Ble Advertiser

Posted on

BLE vs Wi-Fi for IoT: Choosing the Right Protocol on Android

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 with neverForLocation flag for BLUETOOTH_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" /> (or ACCESS_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:

  1. 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_SCAN with neverForLocation helps mitigate this if you don't need location context.
  2. Bluetooth Adapter State: Always check if Bluetooth is enabled on the device. If not, prompt the user to enable it using ACTION_REQUEST_ENABLE.
  3. 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.
  4. ScanCallback Errors: Handle onScanFailed correctly. Common errors include SCAN_FAILED_ALREADY_STARTED, SCAN_FAILED_APPLICATION_REGISTRATION_FAILED, and SCAN_FAILED_FEATURE_UNSUPPORTED.
  5. 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:

  1. 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+.
  2. Network Configuration: Connecting to Wi-Fi programmatically has become more restricted over Android versions for security and privacy.
    • Android 10+ (API 29+): Use WifiNetworkSpecifier with ConnectivityManager for secure provisioning. This allows the user to approve a network suggestion. Directly manipulating WifiConfiguration is largely deprecated or requires system app privileges.
    • Older Android versions: WifiManager.addNetwork() and WifiManager.enableNetwork() were used. This method is less secure and less user-friendly due to lack of explicit user approval.
  3. 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.
  4. 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.
  5. 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
    }
}
Enter fullscreen mode Exit fullscreen mode

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
    }
}
Enter fullscreen mode Exit fullscreen mode

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

  1. Pitfall: Excessive Battery Drain from Scanning. Constant or very frequent BLE scanning in SCAN_MODE_LOW_LATENCY consumes 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. Leverage ScanFilter to only discover devices with specific services or names, reducing irrelevant scan results processing.
  2. 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 onConnectionStateChange to 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 the BluetoothGatt object is still connected using BluetoothManager.getConnectionState(device, BluetoothProfile.GATT). Ensure you call gatt.disconnect() and gatt.close() when a connection is no longer needed or if reconnection attempts fail persistently.
  3. Pitfall: Android Permission Fragmentation and Runtime Issues. Dealing with ACCESS_FINE_LOCATION, BLUETOOTH_SCAN, BLUETOOTH_CONNECT across different Android versions (maxSdkVersion and 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 check Build.VERSION.SDK_INT to apply version-specific permission logic for older devices while preparing for newer ones. Thoroughly test your permission flows on multiple Android versions and devices.

Wi-Fi Best Practices

  1. 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 DTIM intervals 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.
  2. 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+) or NetworkSuggestion to 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.
  3. 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)