Skip to content

Android BLE Advertising — Complete Guide

DodaTech Updated 2026-06-24 2 min read

In this tutorial, you'll learn about Android BLE Advertising. We cover key concepts, practical examples, and best practices to help you understand and apply this topic effectively.

The Problem

Your BLE advertisement doesn't appear on other devices, or the advertised service UUID is not visible to scanners.

Wrong Approach ❌

// Starting advertising without proper setup
val bluetoothLeAdvertiser = BluetoothAdapter.getDefaultAdapter().bluetoothLeAdvertiser
bluetoothLeAdvertiser?.startAdvertising(null, null, advertisingCallback)

Output: Advertisement not visible. No service UUID in scan results.

Right Approach ✅

class BleAdvertiser(private val context: Context) {
    private var bluetoothLeAdvertiser: BluetoothLeAdvertiser? = null
    private var isAdvertising = false

    fun startAdvertising(serviceUuid: String, deviceName: String) {
        if (!hasPermissions()) {
            requestPermissions()
            return
        }

        val adapter = BluetoothAdapter.getDefaultAdapter()
        bluetoothLeAdvertiser = adapter?.bluetoothLeAdvertiser

        if (bluetoothLeAdvertiser == null) {
            Log.e("BleAdv", "BLE advertising not supported on this device")
            return
        }

        // Advertising data
        val advertiseData = AdvertiseData.Builder()
            .setIncludeDeviceName(true)
            .setIncludeTxPowerLevel(true)
            .addServiceUuid(ParcelUuid(UUID.fromString(serviceUuid)))
            .addServiceData(
                ParcelUuid(UUID.fromString(serviceUuid)),
                "active".toByteArray()
            )
            .build()

        // Scan response data (additional data when scanned)
        val scanResponse = AdvertiseData.Builder()
            .setIncludeDeviceName(false) // Already in advertise data
            .addManufacturerData(
                0x1234, // Manufacturer ID
                deviceName.toByteArray()
            )
            .build()

        // Advertising settings
        val advertiseSettings = AdvertiseSettings.Builder()
            .setAdvertiseMode(AdvertiseSettings.ADVERTISE_MODE_LOW_LATENCY) // Fast advertising
            .setTxPowerLevel(AdvertiseSettings.ADVERTISE_TX_POWER_HIGH) // Maximum range
            .setConnectable(true) // Allow connections
            .setTimeout(0) // No timeout
            .build()

        bluetoothLeAdvertiser?.startAdvertising(
            advertiseSettings,
            advertiseData,
            scanResponse,
            object : AdvertiseCallback() {
                override fun onStartSuccess(settingsInEffect: AdvertiseSettings) {
                    isAdvertising = true
                    Log.d("BleAdv", "Advertising started")
                }

                override fun onStartFailure(errorCode: Int) {
                    isAdvertising = false
                    Log.e("BleAdv", "Advertising failed: $errorCode")
                    handleError(errorCode)
                }
            }
        )
    }

    fun stopAdvertising() {
        bluetoothLeAdvertiser?.stopAdvertising(advertisingCallback)
        isAdvertising = false
    }

    private fun handleError(errorCode: Int) {
        when (errorCode) {
            AdvertiseCallback.ADVERTISE_FAILED_ALREADY_STARTED -> {
                stopAdvertising()
                // Retry
            }
            AdvertiseCallback.ADVERTISE_FAILED_FEATURE_UNSUPPORTED -> {
                // BLE advertising not supported
            }
            AdvertiseCallback.ADVERTISE_FAILED_TOO_MANY_ADVERTISERS -> {
                // Another app is advertising
            }
        }
    }

    private fun hasPermissions(): Boolean {
        return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
            ContextCompat.checkSelfPermission(context, Manifest.permission.BLUETOOTH_ADVERTISE) == PERMISSION_GRANTED
        } else {
            ContextCompat.checkSelfPermission(context, Manifest.permission.ACCESS_FINE_LOCATION) == PERMISSION_GRANTED
        }
    }
}

Output: BLE advertisement visible to all nearby scanners.

Prevention

  • Check bluetoothLeAdvertiser != null (not all devices support BLE advertising).
  • Include service UUID in AdvertiseData for easy filtering.
  • Use ADVERTISE_MODE_LOW_LATENCY during active pairing.
  • Use ADVERTISE_TX_POWER_HIGH for maximum range.
  • Handle ADVERTISE_FAILED_TOO_MANY_ADVERTISERS (limit is typically 1-2).

Common Mistakes with bluetooth advertising

  1. Overlapping type class instances that cause GHC to reject the program with ambiguous dispatch errors
  2. Non-exhaustive pattern matches that compile with warnings then crash at runtime
  3. Misunderstanding that String is [Char] with poor performance for large text operations

These mistakes appear frequently in real-world Android code. DodaTech's contributors have identified these patterns through analysis of open-source projects and production systems.

Practice Exercise

Write a pure function that safely divides two integers using Maybe, then test it with edge cases like division by zero and negative numbers.

This exercise reinforces the concepts covered in this guide. Try implementing it before checking online solutions.

FAQ

### What BLE advertising permissions are needed on Android 12+?

You need BLUETOOTH_ADVERTISE permission. On Android 11-, ACCESS_FINE_LOCATION is required.

### What is the difference between advertise data and scan response?

Advertise data is broadcast in every advertising packet (max 31 bytes). Scan response is sent when a scanner requests additional data (max 31 bytes). Use scan response for manufacturer data.

### How often does BLE advertise?

ADVERTISE_MODE_LOW_LATENCY ≈ 100ms interval. ADVERTISE_MODE_BALANCED ≈ 250ms. ADVERTISE_MODE_LOW_POWER ≈ 1000ms. Faster intervals drain more battery.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro