How to Successfully Scan for Bluetooth Android Devices with Low Energy

The Use Case

Scan for all available Android devices supporting BLE, Use any Android device with Android OS starting from 4 to 10 to Scan.

The Problem

1. Successful scanning has inconsistent development requirements over different Android OS2. Official docs are outdated and don’t mention these inconsistencies explicitly, also samples accompanying the docs are using deprecated APIs

Article objectives

This is not a tutorial! this article covers the missing and confusing parts in the Android Developers overview article on Bluetooth Low Energy. It is highly recommended reading.

Technical Solution In Three Steps

1. The first problem I faced was that I needed to make sure that the device’s Bluetooth is not only On but also Visible. That was mentioned explicitly in the documentation but after thorough readings!

Findings: no need to request Bluetooth permission if discoverability is requested, also to stay discoverable infinitely through development set EXTRA_DISCOVERABLE_DURATION to 0, but that was not recommended in real use cases by docs

[pastacode lang="markup" manual="val%20discoverableIntent%20%3DIntent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE)%0AdiscoverableIntent%0A%20%20.putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION%2C0%2F*set%20a%20duration%2C%200%20is%20not%20recommended*%2F)%0AstartActivity(discoverableIntent)" message="" highlight="" provider="manual"/]

2. If your app targets Android 6+, you must not only declare ACCESS_FINE_LOCATION or ACCESS_COARSE_LOCATION in the Manifest, but also request Runtime Permissions in your app, or while in development mode, set your location permissions on for your app explicitly. If you didn’t, it would never be discoverable or even discover other devices.

A- In development mode give permission manually through settings.

[caption id="attachment_70995" align="aligncenter" width="250"]

navigation menu

App permissions settings[/caption]B- or addRuntime Permissionsto activate it momentarily once user asks for scanning[pastacode lang="markup" manual="private%20fun%20allowLocationDetectionPermissions()%20%7B%0A%20%20%20%20%20if%20(ContextCompat.checkSelfPermission(this%40MainActivity%2C%20Manifest.permission.ACCESS_FINE_LOCATION)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%3D%3D%20PackageManager.PERMISSION_DENIED)%20%7B%0A%20%20%20%20%20%20%20%20%20ActivityCompat.requestPermissions(this%40MainActivity%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20arrayOf(Manifest.permission.ACCESS_FINE_LOCATION)%2C%20FINE_LOCATION_PERMISSION_REQUEST)%0A%20%20%20%20%20%7D%0A%7D%0A%0Aoverride%20fun%20onRequestPermissionsResult(requestCode%3A%20Int%2C%20permissions%3A%20Array%3CString%3E%2C%20grantResults%3A%20IntArray)%20%7B%0A%20%20%20%20%20when%20(requestCode)%20%7B%0A%20%20%20%20%20%20%20%20%20FINE_LOCATION_PERMISSION_REQUEST%20-%3E%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20if%20((grantResults.isNotEmpty()%20%26%26%20grantResults%5B0%5D%20%3D%3D%20PackageManager.PERMISSION_GRANTED))%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20scanLeDevice(true)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%7D%20else%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%2F%2Fnotify%20the%20user%20to%20allow%20location%20detection%20otherwise%20the%20scaning%20won't%20work%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20%20%20%20%20%20return%0A%20%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%7D%0A%7D" message="" highlight="" provider="manual"/]

3. Please use latest BluetoothLeScanner API to start and end scanning instead of bluetoothAdapter instance, this was the outdated part in the docs

[pastacode lang="markup" manual="private%20fun%20scanLeDevice(enable%3A%20Boolean)%20%7B%0A%20%20%20%20%20%20%20%20when%20(enable)%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20true%20-%3E%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%2F%2F%20Stops%20scanning%20after%20a%20pre-defined%20scan%20period.%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20Handler().postDelayed(%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20mScanning%20%3D%20false%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20bluetoothAdapter%3F.bluetoothLeScanner%3F.stopScan(mLeScanCallback)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%7D%2C%205000)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20mScanning%20%3D%20true%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20bluetoothAdapter%3F.bluetoothLeScanner%3F.startScan(mLeScanCallback)%0A%20%20%20%20%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20%20%20%20%20else%20-%3E%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20mScanning%20%3D%20false%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20bluetoothAdapter%3F.bluetoothLeScanner%3F.stopScan(mLeScanCallback)%0A%20%20%20%20%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20%7D%0A%0A%20%20%20%20%7D%0A%0A%20private%20var%20mLeScanCallback%3A%20ScanCallback%20%3D%0A%20%20%20%20%20%20%20%20%20%20%20%20object%20%3A%20ScanCallback()%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20override%20fun%20onScanResult(callbackType%3A%20Int%2C%20result%3A%20ScanResult%3F)%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20super.onScanResult(callbackType%2C%20result)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%2F%2FDo%20your%20thing%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%7D%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20override%20fun%20onBatchScanResults(results%3A%20List%3CScanResult%3F%3E%3F)%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20super.onBatchScanResults(results)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%2F%2FDo%20your%20thing%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%7D%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20override%20fun%20onScanFailed(errorCode%3A%20Int)%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20super.onScanFailed(errorCode)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%2F%2FDo%20your%20thing%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20%20%20%20%20%7D" message="" highlight="" provider="manual"/]

Then voila! All devices from Android 4 to 10 will be scannable in your app.

Please, check the full sample app code here

Related posts

The latest articles from Andela.

Visit our blog

Overcoming the Challenges of Working With a Mobile FinTech API

Andela community member Zzwia Raymond explores why, despite the potential of the MTN Mobile Money platform and its API, there are technical hurdles, from complex documentation to enhancing functionality.

How Andela Transformed Tech Hiring in 10 Years

Celebrating 10 years of transforming tech hiring by unlocking global talent across Africa, Latin America and beyond, Andela has surpassed its original goal by training nearly 110,000 technologists and assembling one of the world's largest remote tech talent marketplaces.

What GPT-4o and Gemini releases mean for AI

The latest generative AI models from OpenAI (GPT-4) and Google (Gemini 1.5 Pro, Veo, etc.) promise improved capabilities, lower costs, and transformative applications across various industries by integrating advanced AI technologies into business operations.

We have a 96%+
talent match success rate.

The Andela Talent Operating Platform provides transparency to talent profiles and assessment before hiring. AI-driven algorithms match the right talent for the job.