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

How to hire a Python developer: A guide to finding the right fit

Figuring out how to hire a Python developer? Our guide covers all you need to know, from defining job requirements to finding the right fit for your team!

Top takeaways from Gartner IT Symposium

As the symposium concluded, it became evident that the journey into the AI-driven future is both challenging and exhilarating for IT leaders.

Android ML face detection with Camera X

In this Writer's Room tutorial, Andela Community member Stephen Henry explains how to integrate ML face detection into an Android app using CameraX.

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.