I\'m developing an app that have to connect with a BLE device, in my code I want to use the new Scan and ScanCallback for BLE implemented from API 21 (Android 5) but I have
Your code is crashing because it is creating anonymous inner class. Hence at run time it doesn't find that sCanCallback class.
Try below way and share the outcome. Before you try this, make sure you comment the callback(ScanCallback).
if (Build.VERSION.SDK_INT >= 21) {
mLEScanner.startScan(filters, settings, new ScanCallback() {
@Override
public void onScanResult(int callbackType, ScanResult result) {
BluetoothDevice btDevice = result.getDevice();
connectToDevice(btDevice);
}
public void connectToDevice(BluetoothDevice device) {
if (mGatt == null) {
mGatt = device.connectGatt(context, false, btleGattCallback);
if (Build.VERSION.SDK_INT < 21) {
btAdapter.stopLeScan(leScanCallback);
} else {
mLEScanner.stopScan(mScanCallback);
}
}
}
};
} else {
btAdapter.startLeScan(leScanCallback);
}
After reading several posts I did the following. Just in case, here is the documentation of Android about BluetoothLe
First
Create two methods one scanLeDevice21
and scanLeDevice18
. On scanLeDevice21
add the annotation @RequiresApi(21)
that says:
Denotes that the annotated element should only be called on the given API level or higher. This is similar in purpose to the older @TargetApi annotation, but more clearly expresses that this is a requirement on the caller, rather than being used to "suppress" warnings within the method that exceed the minSdkVersion.
Second
Implement each method, here's my code.
@RequiresApi(21)
private void scanLeDevice21(final boolean enable) {
ScanCallback mLeScanCallback = new ScanCallback() {
@Override
public void onScanResult(int callbackType, ScanResult result) {
super.onScanResult(callbackType, result);
BluetoothDevice bluetoothDevice = result.getDevice();
if (!bluetoothDeviceList.contains(bluetoothDevice)) {
Log.d("DEVICE", bluetoothDevice.getName() + "[" + bluetoothDevice.getAddress() + "]");
bluetoothDeviceArrayAdapter.add(bluetoothDevice);
bluetoothDeviceArrayAdapter.notifyDataSetChanged();
}
}
@Override
public void onBatchScanResults(List<ScanResult> results) {
super.onBatchScanResults(results);
}
@Override
public void onScanFailed(int errorCode) {
super.onScanFailed(errorCode);
}
};
final BluetoothLeScanner bluetoothLeScanner = mBluetoothAdapter.getBluetoothLeScanner();
if (enable) {
// Stops scanning after a pre-defined scan period.
mHandler.postDelayed(() -> {
mScanning = false;
swipeRefreshLayout.setRefreshing(false);
bluetoothLeScanner.stopScan(mLeScanCallback);
}, SCAN_PERIOD);
mScanning = true;
bluetoothLeScanner.startScan(mLeScanCallback);
} else {
mScanning = false;
bluetoothLeScanner.stopScan(mLeScanCallback);
}
}
/**
* Scan BLE devices on Android API 18 to 20
*
* @param enable Enable scan
*/
private void scanLeDevice18(boolean enable) {
BluetoothAdapter.LeScanCallback mLeScanCallback =
new BluetoothAdapter.LeScanCallback() {
@Override
public void onLeScan(final BluetoothDevice bluetoothDevice, int rssi,
byte[] scanRecord) {
getActivity().runOnUiThread(new Runnable() {
@Override
public void run() {
bluetoothDeviceArrayAdapter.add(bluetoothDevice);
bluetoothDeviceArrayAdapter.notifyDataSetChanged();
}
});
}
};
if (enable) {
// Stops scanning after a pre-defined scan period.
mHandler.postDelayed(() -> {
mScanning = false;
mBluetoothAdapter.stopLeScan(mLeScanCallback);
}, SCAN_PERIOD);
mScanning = true;
mBluetoothAdapter.startLeScan(mLeScanCallback);
} else {
mScanning = false;
mBluetoothAdapter.stopLeScan(mLeScanCallback);
}
}
Third
Every time you need to scan devices you surround your code asking which version you are. For instance, I have a RefreshLayout
to display the list of devices. Here's the result:
/**
* Refresh listener
*/
private void refreshScan() {
if (!hasFineLocationPermissions()) {
swipeRefreshLayout.setRefreshing(false);
//Up to marshmallow you need location permissions to scan bluetooth devices, this method is not here since is up to you to implement it and it is out of scope of this question.
requestFineLocationPermission();
} else {
swipeRefreshLayout.setRefreshing(true);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
scanLeDevice21(true);
} else {
scanLeDevice18(true);
}
}
}
And that's it.
Forget about extending, subclassing classes you don't really need like ulusoyca answer.
AbstractBluetoothLe
class and IBleScanCallback
interface. IBleScanCallback
interface is a Marker Interface. In other saying, an interface with no methods. You can also add methods to interface if you need. These methods will do the same functionality for all type of scanCallbacks i.e. getListOfFoundBleDevices(), clearListOfFoundBleDevices() etc.BluetootLeLollipop
, and BluetoothLeJellyBean
classes which extend AbstractBluetoothLe
class. Create also BluetootLeMarshmallow
class which extends BluetootLeLollipop
class. AbstractBluetoothLe
class has protected field mIBleScanCallback
which is an IBleScanCallback
object. BleScanCallbackBase
class which implements IBleScanCallback
.LollipopScanCallback
class which extends ScanCallback
class and implements IBleScanCallback
interface. .This class has a protected field scanCallback
which will be instantiated as BleScanCallbackBase
object. Create also MarshmallowScanCallback
class which extends LollipopScanCallback
class.JellyBeanScanCallback
class which extends BleScanCallbackBase
and implements BluetoothAdapter.LeScanCallback
BleScanCallbackBase
override the method: onScanCallback(...)
LollipoScanCallback
override onScanResult(int callbackType, ScanResult result)
and inside this method call the method onScanCallback(...)
of the scanCallback
object.JellyBeanScanCallback
override onLeScan(final BluetoothDevice device, int rssi, byte[] scanRecord)
and inside this method call onScanCallback(...)
onScanCallback(...)
method of BleScanCallbackBase
class.In short, read about composition over inheritance- I know this is not an answer to your question but this is a neat way of what you want to achieve in the end. Here is the class diagram:
I would like to share my idea to overcome this crash on devices below API 21. The ScanCallback
class has support from API 21. So when you write a code to implement ScanCallback in your scan code it will cause a crash on lover APIs. I have fixed it the following way:
I created one new abstract class which extending ScanCallback class as follows:
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public abstract class AppScanCallback extends ScanCallback {}
Now I am using this class instance in my BluetoothService class as follow:
@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
private AppScanCallback mScanCallback;
And using this variable as follow:
public void startBleScan() {
if (isEnabled()) {
if (Build.VERSION.SDK_INT < 21) {
_bluetoothAdapter.startLeScan(mLeScanCallback);
} else {
mLEScanner = _bluetoothAdapter.getBluetoothLeScanner();
settings = new ScanSettings.Builder()
.build();
filters = new ArrayList<>();
mScanCallback = new AppScanCallback() {
@Override
public void onScanResult(int callbackType, ScanResult result) {
if (Build.VERSION.SDK_INT >= 21) {
BluetoothDevice btDevice = result.getDevice();
onLeScanResult(btDevice, result.getScanRecord().getBytes());
}
}
@Override
public void onBatchScanResults(List<ScanResult> results) {
for (ScanResult sr : results) {
Log.i("ScanResult - Results", sr.toString());
}
}
@Override
public void onScanFailed(int errorCode) {
Log.e("Scan Failed", "Error Code: " + errorCode);
}
};
mLEScanner.startScan(filters, settings, mScanCallback);
}
}
}
and to stop scan
public void stopBLEScan() {;
if (Build.VERSION.SDK_INT < 21) {
_bluetoothAdapter.stopLeScan(mLeScanCallback);
} else {
mLEScanner.stopScan(mScanCallback);
}
}
Hope it will help you out.