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 OS

2. 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

val discoverableIntent =Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE)
discoverableIntent
  .putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION,0/*set a duration, 0 is not recommended*/)
startActivity(discoverableIntent)

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.

App permissions settings

B- or add Runtime Permissions to activate it momentarily once user asks for scanning

private fun allowLocationDetectionPermissions() {
     if (ContextCompat.checkSelfPermission(this@MainActivity, Manifest.permission.ACCESS_FINE_LOCATION)
             == PackageManager.PERMISSION_DENIED) {
         ActivityCompat.requestPermissions(this@MainActivity,
                 arrayOf(Manifest.permission.ACCESS_FINE_LOCATION), FINE_LOCATION_PERMISSION_REQUEST)
     }
}

override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>, grantResults: IntArray) {
     when (requestCode) {
         FINE_LOCATION_PERMISSION_REQUEST -> {
             if ((grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED)) {
                 scanLeDevice(true)
             } else {
                 //notify the user to allow location detection otherwise the scaning won't work
             }
             return
         }
     }
}

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

private fun scanLeDevice(enable: Boolean) {
        when (enable) {
            true -> {
                // Stops scanning after a pre-defined scan period.
                Handler().postDelayed({
                    mScanning = false
                    bluetoothAdapter?.bluetoothLeScanner?.stopScan(mLeScanCallback)
                }, 5000)
                mScanning = true
                bluetoothAdapter?.bluetoothLeScanner?.startScan(mLeScanCallback)
            }
            else -> {
                mScanning = false
                bluetoothAdapter?.bluetoothLeScanner?.stopScan(mLeScanCallback)
            }
        }

    }

 private var mLeScanCallback: ScanCallback =
            object : ScanCallback() {
                override fun onScanResult(callbackType: Int, result: ScanResult?) {
                    super.onScanResult(callbackType, result)
                    //Do your thing
                }

                override fun onBatchScanResults(results: List<ScanResult?>?) {
                    super.onBatchScanResults(results)
                    //Do your thing
                }

                override fun onScanFailed(errorCode: Int) {
                    super.onScanFailed(errorCode)
                    //Do your thing
                }
            }

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

Please, check the full sample app code here

featured_image
About the Author

Aya Salama

Software Engineer at Andela Egypt

More Insights

April 15, 2020

How to Scan for Android Bluetooth Low Energy Devices Successfully

Aya Salama

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 OS

2. 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

val discoverableIntent =Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE)
discoverableIntent
  .putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION,0/*set a duration, 0 is not recommended*/)
startActivity(discoverableIntent)

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.

App permissions settings

B- or add Runtime Permissions to activate it momentarily once user asks for scanning

private fun allowLocationDetectionPermissions() {
     if (ContextCompat.checkSelfPermission(this@MainActivity, Manifest.permission.ACCESS_FINE_LOCATION)
             == PackageManager.PERMISSION_DENIED) {
         ActivityCompat.requestPermissions(this@MainActivity,
                 arrayOf(Manifest.permission.ACCESS_FINE_LOCATION), FINE_LOCATION_PERMISSION_REQUEST)
     }
}

override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>, grantResults: IntArray) {
     when (requestCode) {
         FINE_LOCATION_PERMISSION_REQUEST -> {
             if ((grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED)) {
                 scanLeDevice(true)
             } else {
                 //notify the user to allow location detection otherwise the scaning won't work
             }
             return
         }
     }
}

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

private fun scanLeDevice(enable: Boolean) {
        when (enable) {
            true -> {
                // Stops scanning after a pre-defined scan period.
                Handler().postDelayed({
                    mScanning = false
                    bluetoothAdapter?.bluetoothLeScanner?.stopScan(mLeScanCallback)
                }, 5000)
                mScanning = true
                bluetoothAdapter?.bluetoothLeScanner?.startScan(mLeScanCallback)
            }
            else -> {
                mScanning = false
                bluetoothAdapter?.bluetoothLeScanner?.stopScan(mLeScanCallback)
            }
        }

    }

 private var mLeScanCallback: ScanCallback =
            object : ScanCallback() {
                override fun onScanResult(callbackType: Int, result: ScanResult?) {
                    super.onScanResult(callbackType, result)
                    //Do your thing
                }

                override fun onBatchScanResults(results: List<ScanResult?>?) {
                    super.onBatchScanResults(results)
                    //Do your thing
                }

                override fun onScanFailed(errorCode: Int) {
                    super.onScanFailed(errorCode)
                    //Do your thing
                }
            }

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

Please, check the full sample app code here

featured_image
About the Author

Aya Salama

Software Engineer at Andela Egypt

Thanks for subscribing!

 

More Insights

Partners in Delivery: Andela’s Model of Remote Software Engineering Staff Augmentation

In the wake of the COVID-19 pandemic, businesses are looking to streamline operations and find new ...

4_June_2020

How to keep engaging your tech community in a fully-remote world

In-person (offline) tech community meetups have, for the longest time, been the major engagement pl...

29_May_2020

The complete guide to Debug Swift code with LLDB

This guide contains the following content to ease your journey to become an lldb ninja: — Obje...

27_May_2020

Tips for Handling Remote Team Emergencies

Guest post by Ashley Kent. If your company is in the process of transitioning to a remote team, ...

27_May_2020

Remote Engineering Staff Augmentation Aids the Race to Digitize

While the economic shock and aftershocks of the global pandemic continue to ripple through the econ...

26_May_2020

Partners

Tap into a global talent pool and hire the “right” developers in days, not months.

Developers

Accelerate your career by working with high-performing engineering teams around the world.

BECOME A DEVELOPER

Hire Developers

We take great pride in matching our developers with the best partners. Tell us about your team below!

preloader_image

Thank you for your interest

A member of our team will reach out to you soon.