diff --git a/CHANGELOG.md b/CHANGELOG.md
index 61846c7..9ee05db 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,8 @@
+## 0.0.2
+* Added RSSI value, alias name and device type added to BluetoothDevice
+* Scan results und bonded devices are now filtered correctly to only return BL Classic devices
+* Example app updated: Tap on a scan result to connect to it and send and receive messages.
+
## 0.0.1
* Initial release: Scan for and connect to BL Classic devices.
diff --git a/README.md b/README.md
index 4417f52..433631f 100644
--- a/README.md
+++ b/README.md
@@ -50,19 +50,16 @@ In the **android/app/src/main/AndroidManifest.xml** add:
```xml
-
+
+
-
+
+
+
-
+
```
#### With location access
@@ -71,18 +68,16 @@ In the **android/app/src/main/AndroidManifest.xml** add:
```xml
-
+
+
+
-
+
+
-
+
```
Then pass the `accessFineLocation` parameter when initializing the plugin:
diff --git a/android/src/main/kotlin/dev/lenhart/flutter_blue_classic/AdapterStateReceiver.kt b/android/src/main/kotlin/dev/lenhart/flutter_blue_classic/AdapterStateReceiver.kt
index 4ac8eb1..2b1f5be 100644
--- a/android/src/main/kotlin/dev/lenhart/flutter_blue_classic/AdapterStateReceiver.kt
+++ b/android/src/main/kotlin/dev/lenhart/flutter_blue_classic/AdapterStateReceiver.kt
@@ -6,8 +6,8 @@ import android.content.Context
import android.content.Intent
import io.flutter.plugin.common.EventChannel
-class AdapterStateReceiver : EventChannel.StreamHandler{
- companion object{
+class AdapterStateReceiver : EventChannel.StreamHandler {
+ companion object {
const val CHANNEL_NAME: String = "${BlueClassicHelper.NAMESPACE}/adapterState"
}
@@ -30,11 +30,13 @@ class AdapterStateReceiver : EventChannel.StreamHandler{
val adapterState =
intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.ERROR)
- adapterStateEventSink.let { it?.success(
- BlueClassicHelper.adapterStateString(
- adapterState
+ adapterStateEventSink.let {
+ it?.success(
+ BlueClassicHelper.adapterStateString(
+ adapterState
+ )
)
- ) }
+ }
}
}
}
\ No newline at end of file
diff --git a/android/src/main/kotlin/dev/lenhart/flutter_blue_classic/BlueClassicHelper.kt b/android/src/main/kotlin/dev/lenhart/flutter_blue_classic/BlueClassicHelper.kt
index 1424ddb..481c1ba 100644
--- a/android/src/main/kotlin/dev/lenhart/flutter_blue_classic/BlueClassicHelper.kt
+++ b/android/src/main/kotlin/dev/lenhart/flutter_blue_classic/BlueClassicHelper.kt
@@ -1,13 +1,16 @@
package dev.lenhart.flutter_blue_classic
+import android.Manifest
+import android.annotation.TargetApi
import android.bluetooth.BluetoothAdapter
import android.bluetooth.BluetoothDevice
+import androidx.annotation.RequiresPermission
class BlueClassicHelper {
companion object {
const val NAMESPACE: String = "blue_classic"
const val METHOD_CHANNEL_NAME: String = "$NAMESPACE/methods"
- const val ERROR_ADDRESS_INVALID : String= "addressInvalid"
+ const val ERROR_ADDRESS_INVALID: String = "addressInvalid"
fun adapterStateString(state: Int): String {
return when (state) {
@@ -29,11 +32,30 @@ class BlueClassicHelper {
}
}
- fun bluetoothDeviceToMap(device: BluetoothDevice): MutableMap {
- val entry: MutableMap = HashMap()
+ fun deviceTypeString(type: Int): String {
+ return when (type) {
+ BluetoothDevice.DEVICE_TYPE_LE -> "le"
+ BluetoothDevice.DEVICE_TYPE_CLASSIC -> "classic"
+ BluetoothDevice.DEVICE_TYPE_DUAL -> "dual"
+ else -> "unknown"
+ }
+ }
+
+ @TargetApi(34)
+ @RequiresPermission(value = Manifest.permission.BLUETOOTH_CONNECT)
+ fun bluetoothDeviceToMap(
+ device: BluetoothDevice,
+ rssi: Short? = null
+ ): MutableMap {
+ val entry: MutableMap = HashMap()
entry["address"] = device.address
- entry["name"] = device.name ?: ""
+ entry["name"] = device.name
+ if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.R) {
+ entry["alias"] = device.alias
+ }
entry["bondState"] = bondStateString(device.bondState)
+ entry["deviceType"] = deviceTypeString(device.type)
+ entry["rssi"] = rssi
return entry
}
}
diff --git a/android/src/main/kotlin/dev/lenhart/flutter_blue_classic/FlutterBlueClassicPlugin.kt b/android/src/main/kotlin/dev/lenhart/flutter_blue_classic/FlutterBlueClassicPlugin.kt
index 5a8b9ee..40d1b4b 100644
--- a/android/src/main/kotlin/dev/lenhart/flutter_blue_classic/FlutterBlueClassicPlugin.kt
+++ b/android/src/main/kotlin/dev/lenhart/flutter_blue_classic/FlutterBlueClassicPlugin.kt
@@ -29,474 +29,473 @@ import io.flutter.plugin.common.MethodChannel.Result
import java.util.concurrent.Executors
/** FlutterBlueClassicPlugin */
-class FlutterBlueClassicPlugin: FlutterPlugin, MethodCallHandler, ActivityAware {
-
- companion object {
- const val TAG: String = "FlutterBlueClassic"
- }
-
- /// The MethodChannel that will the communication between Flutter and native Android
- ///
- /// This local reference serves to register the plugin with the Flutter Engine and unregister it
- /// when the Flutter Engine is detached from the Activity
- private lateinit var methodChannel : MethodChannel
-
- private var activityPluginBinding: ActivityPluginBinding? = null
- private var binaryMessenger: BinaryMessenger? = null
-
- private lateinit var adapterStateChannel: EventChannel
- private lateinit var adapterStateReceiver: AdapterStateReceiver
-
- private lateinit var scanResultChannel: EventChannel
- private lateinit var scanResultReceiver: ScanResultReceiver
-
- private lateinit var discoveryStateChannel: EventChannel
- private lateinit var discoveryStateReceiver: DiscoveryStateReceiver
-
- private lateinit var permissionManager: PermissionManager
- private var bluetoothAdapter: BluetoothAdapter? = null
-
- private val connections = SparseArray(2)
- private var lastConnectionId = 0
-
- private var context: Application? = null
-
- override fun onAttachedToEngine(flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {
- methodChannel =
- MethodChannel(
- flutterPluginBinding.binaryMessenger,
- BlueClassicHelper.METHOD_CHANNEL_NAME
- )
- methodChannel.setMethodCallHandler(this)
- binaryMessenger = flutterPluginBinding.binaryMessenger
-
- // Adapter State Stream
- adapterStateReceiver = AdapterStateReceiver()
- adapterStateChannel =
- EventChannel(flutterPluginBinding.binaryMessenger, AdapterStateReceiver.CHANNEL_NAME)
- adapterStateChannel.setStreamHandler(adapterStateReceiver)
- val filterAdapter = IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED)
- flutterPluginBinding.applicationContext.registerReceiver(
- adapterStateReceiver.mBluetoothAdapterStateReceiver,
- filterAdapter
- )
-
- // Scan Result Stream
- scanResultReceiver = ScanResultReceiver()
- scanResultChannel =
- EventChannel(flutterPluginBinding.binaryMessenger, ScanResultReceiver.CHANNEL_NAME)
- scanResultChannel.setStreamHandler(scanResultReceiver)
- val filterScanResults = IntentFilter(BluetoothDevice.ACTION_FOUND)
- flutterPluginBinding.applicationContext.registerReceiver(
- scanResultReceiver.scanResultReceiver,
- filterScanResults
- )
-
- // Discovery State Stream
- discoveryStateReceiver = DiscoveryStateReceiver()
- discoveryStateChannel =
- EventChannel(flutterPluginBinding.binaryMessenger, DiscoveryStateReceiver.CHANNEL_NAME)
- discoveryStateChannel.setStreamHandler(discoveryStateReceiver)
- val filterDiscoveryState = IntentFilter()
- filterDiscoveryState.addAction(BluetoothAdapter.ACTION_DISCOVERY_STARTED)
- filterDiscoveryState.addAction(BluetoothAdapter.ACTION_DISCOVERY_FINISHED)
- flutterPluginBinding.applicationContext.registerReceiver(
- discoveryStateReceiver.discoveryStateReceiver,
- filterDiscoveryState
- )
-
- this.context = flutterPluginBinding.applicationContext as Application
-
- val bluetoothManager =
- getSystemService(flutterPluginBinding.applicationContext, BluetoothManager::class.java)
- bluetoothAdapter = bluetoothManager?.adapter
-
- }
-
- override fun onAttachedToActivity(binding: ActivityPluginBinding) {
- activityPluginBinding = binding
- permissionManager = PermissionManager(context!!.applicationContext, binding.activity)
- binding.addRequestPermissionsResultListener(permissionManager)
- }
-
- override fun onDetachedFromActivity() {
- activityPluginBinding?.removeRequestPermissionsResultListener(permissionManager)
- activityPluginBinding = null
- permissionManager.setActivity(null)
- }
-
- override fun onDetachedFromActivityForConfigChanges() {
- onDetachedFromActivity()
- }
-
- override fun onReattachedToActivityForConfigChanges(binding: ActivityPluginBinding) {
- onAttachedToActivity(binding)
- }
-
- override fun onMethodCall(call: MethodCall, result: Result) {
- when (call.method) {
- "isSupported" -> result.success(checkBluetoothSupport())
- "isEnabled" -> result.success(isBluetoothEnabled())
- "turnOn" -> turnOn(result)
- "getAdapterState" -> result.success(getAdapterState())
- "bondedDevices" -> getBondedDevices(result)
- "startScan" -> startScan(result, call.argument("usesFineLocation") ?: false)
- "stopScan" -> stopScan(result)
- "isScanningNow" -> isScanningNow(result)
- "bondDevice" -> bondDevice(result, call.argument("address") ?: "")
- "connect" -> connect(result, call.argument("address") ?: "")
- "write" -> {
- val id = call.argument("id")
- val bytes = call.argument("bytes")
- if (id != null && bytes != null) {
- write(result, id, bytes)
- } else {
- result.error("argumentError", "Not all required arguments were specified", null)
- }
- }
+class FlutterBlueClassicPlugin : FlutterPlugin, MethodCallHandler, ActivityAware {
- else -> {
- result.notImplemented()
- }
+ companion object {
+ const val TAG: String = "FlutterBlueClassic"
}
- }
- override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) {
- methodChannel.setMethodCallHandler(null)
- adapterStateChannel.setStreamHandler(null)
- binaryMessenger = null
+ /// The MethodChannel that will the communication between Flutter and native Android
+ ///
+ /// This local reference serves to register the plugin with the Flutter Engine and unregister it
+ /// when the Flutter Engine is detached from the Activity
+ private lateinit var methodChannel: MethodChannel
- binding.applicationContext.unregisterReceiver(adapterStateReceiver.mBluetoothAdapterStateReceiver)
- binding.applicationContext.unregisterReceiver(scanResultReceiver.scanResultReceiver)
- binding.applicationContext.unregisterReceiver(discoveryStateReceiver.discoveryStateReceiver)
- }
+ private var activityPluginBinding: ActivityPluginBinding? = null
+ private var binaryMessenger: BinaryMessenger? = null
+ private lateinit var adapterStateChannel: EventChannel
+ private lateinit var adapterStateReceiver: AdapterStateReceiver
+ private lateinit var scanResultChannel: EventChannel
+ private lateinit var scanResultReceiver: ScanResultReceiver
+ private lateinit var discoveryStateChannel: EventChannel
+ private lateinit var discoveryStateReceiver: DiscoveryStateReceiver
- private fun checkBluetoothSupport(): Boolean {
- return context?.packageManager?.hasSystemFeature(PackageManager.FEATURE_BLUETOOTH) ?: false
- }
+ private lateinit var permissionManager: PermissionManager
+ private var bluetoothAdapter: BluetoothAdapter? = null
- private fun isBluetoothEnabled(): Boolean {
- return bluetoothAdapter?.isEnabled ?: false
- }
+ private val connections = SparseArray(2)
+ private var lastConnectionId = 0
- private fun turnOn(result: Result) {
- if (isBluetoothEnabled()) {
- result.success(null)
- return
- }
- val permissions = ArrayList()
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
- permissions.add(Manifest.permission.BLUETOOTH_CONNECT)
- }
- permissionManager.ensurePermissions(permissions.toTypedArray()) { success: Boolean, deniedPermissions: List? ->
- run {
- try {
- if (success) {
- val enableBtIntent = Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE)
- startActivityForResult(
- activityPluginBinding!!.activity,
- enableBtIntent,
- PermissionManager.REQUEST_ENABLE_BT,
- null
+ private var context: Application? = null
+
+ override fun onAttachedToEngine(flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {
+ methodChannel =
+ MethodChannel(
+ flutterPluginBinding.binaryMessenger,
+ BlueClassicHelper.METHOD_CHANNEL_NAME
)
- result.success(null)
- return@run
- }
- } catch (_: Exception) {
- }
- result.error(
- PermissionManager.ERROR_PERMISSION_DENIED,
- String.format(
- "Required permission(s) %s denied",
- deniedPermissions?.joinToString() ?: ""
- ), null
+ methodChannel.setMethodCallHandler(this)
+ binaryMessenger = flutterPluginBinding.binaryMessenger
+
+ // Adapter State Stream
+ adapterStateReceiver = AdapterStateReceiver()
+ adapterStateChannel =
+ EventChannel(flutterPluginBinding.binaryMessenger, AdapterStateReceiver.CHANNEL_NAME)
+ adapterStateChannel.setStreamHandler(adapterStateReceiver)
+ val filterAdapter = IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED)
+ flutterPluginBinding.applicationContext.registerReceiver(
+ adapterStateReceiver.mBluetoothAdapterStateReceiver,
+ filterAdapter
+ )
+
+ // Scan Result Stream
+ scanResultReceiver = ScanResultReceiver()
+ scanResultChannel =
+ EventChannel(flutterPluginBinding.binaryMessenger, ScanResultReceiver.CHANNEL_NAME)
+ scanResultChannel.setStreamHandler(scanResultReceiver)
+ val filterScanResults = IntentFilter(BluetoothDevice.ACTION_FOUND)
+ flutterPluginBinding.applicationContext.registerReceiver(
+ scanResultReceiver.scanResultReceiver,
+ filterScanResults
+ )
+
+ // Discovery State Stream
+ discoveryStateReceiver = DiscoveryStateReceiver()
+ discoveryStateChannel =
+ EventChannel(flutterPluginBinding.binaryMessenger, DiscoveryStateReceiver.CHANNEL_NAME)
+ discoveryStateChannel.setStreamHandler(discoveryStateReceiver)
+ val filterDiscoveryState = IntentFilter()
+ filterDiscoveryState.addAction(BluetoothAdapter.ACTION_DISCOVERY_STARTED)
+ filterDiscoveryState.addAction(BluetoothAdapter.ACTION_DISCOVERY_FINISHED)
+ flutterPluginBinding.applicationContext.registerReceiver(
+ discoveryStateReceiver.discoveryStateReceiver,
+ filterDiscoveryState
)
- }
+ this.context = flutterPluginBinding.applicationContext as Application
+
+ val bluetoothManager =
+ getSystemService(flutterPluginBinding.applicationContext, BluetoothManager::class.java)
+ bluetoothAdapter = bluetoothManager?.adapter
+
}
- }
-
- private fun getAdapterState(): String {
- return bluetoothAdapter?.state?.let { BlueClassicHelper.adapterStateString(it) }
- ?: "unavailable"
- }
-
- @SuppressLint("MissingPermission")
- private fun getBondedDevices(result: Result) {
- val permissions = ArrayList()
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
- permissions.add(Manifest.permission.BLUETOOTH_CONNECT)
+
+ override fun onAttachedToActivity(binding: ActivityPluginBinding) {
+ activityPluginBinding = binding
+ permissionManager = PermissionManager(context!!.applicationContext, binding.activity)
+ binding.addRequestPermissionsResultListener(permissionManager)
}
- permissionManager.ensurePermissions(permissions.toTypedArray()) { success: Boolean, deniedPermissions: List? ->
- run {
- if (success) {
- val devices: List>? =
- bluetoothAdapter?.bondedDevices?.map {
- BlueClassicHelper.bluetoothDeviceToMap(it)
- }
- result.success(devices)
- } else {
- result.error(
- PermissionManager.ERROR_PERMISSION_DENIED,
- String.format(
- "Required permission(s) %s denied",
- deniedPermissions?.joinToString() ?: ""
- ), null
- )
- }
- }
+ override fun onDetachedFromActivity() {
+ activityPluginBinding?.removeRequestPermissionsResultListener(permissionManager)
+ activityPluginBinding = null
+ permissionManager.setActivity(null)
}
- }
-
- @SuppressLint("MissingPermission")
- private fun startScan(result: Result, usesFineLocation: Boolean) {
- val permissions = ArrayList()
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
- permissions.add(Manifest.permission.BLUETOOTH_SCAN)
- permissions.add(Manifest.permission.BLUETOOTH_CONNECT)
+
+ override fun onDetachedFromActivityForConfigChanges() {
+ onDetachedFromActivity()
}
- if (usesFineLocation) {
- permissions.add(Manifest.permission.ACCESS_FINE_LOCATION)
+
+ override fun onReattachedToActivityForConfigChanges(binding: ActivityPluginBinding) {
+ onAttachedToActivity(binding)
}
- permissionManager.ensurePermissions(permissions.toTypedArray()) { success: Boolean, deniedPermissions: List? ->
- run {
- if (success) {
- val discoveryStartState = bluetoothAdapter?.startDiscovery()
- result.success(discoveryStartState)
- } else {
- result.error(
- PermissionManager.ERROR_PERMISSION_DENIED,
- String.format(
- "Required permission(s) %s denied",
- deniedPermissions?.joinToString() ?: ""
- ), null
- )
+ override fun onMethodCall(call: MethodCall, result: Result) {
+ when (call.method) {
+ "isSupported" -> result.success(checkBluetoothSupport())
+ "isEnabled" -> result.success(isBluetoothEnabled())
+ "turnOn" -> turnOn(result)
+ "getAdapterState" -> result.success(getAdapterState())
+ "bondedDevices" -> getBondedDevices(result)
+ "startScan" -> startScan(result, call.argument("usesFineLocation") ?: false)
+ "stopScan" -> stopScan(result)
+ "isScanningNow" -> isScanningNow(result)
+ "bondDevice" -> bondDevice(result, call.argument("address") ?: "")
+ "connect" -> connect(result, call.argument("address") ?: "")
+ "write" -> {
+ val id = call.argument("id")
+ val bytes = call.argument("bytes")
+ if (id != null && bytes != null) {
+ write(result, id, bytes)
+ } else {
+ result.error("argumentError", "Not all required arguments were specified", null)
+ }
+ }
+
+ else -> {
+ result.notImplemented()
+ }
}
- }
}
- }
- @SuppressLint("MissingPermission")
- private fun stopScan(result: Result) {
- val permissions = ArrayList()
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
- permissions.add(Manifest.permission.BLUETOOTH_SCAN)
+ override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) {
+ methodChannel.setMethodCallHandler(null)
+ adapterStateChannel.setStreamHandler(null)
+ binaryMessenger = null
+
+ binding.applicationContext.unregisterReceiver(adapterStateReceiver.mBluetoothAdapterStateReceiver)
+ binding.applicationContext.unregisterReceiver(scanResultReceiver.scanResultReceiver)
+ binding.applicationContext.unregisterReceiver(discoveryStateReceiver.discoveryStateReceiver)
}
- permissionManager.ensurePermissions(permissions.toTypedArray()) { success: Boolean, deniedPermissions: List? ->
- run {
- if (success) {
- result.success(bluetoothAdapter?.cancelDiscovery())
- } else {
- result.error(
- PermissionManager.ERROR_PERMISSION_DENIED,
- String.format(
- "Required permission(s) %s denied",
- deniedPermissions?.joinToString() ?: ""
- ), null
- )
- }
- }
+
+ private fun checkBluetoothSupport(): Boolean {
+ return context?.packageManager?.hasSystemFeature(PackageManager.FEATURE_BLUETOOTH) ?: false
}
- }
- @SuppressLint("MissingPermission")
- private fun isScanningNow(result: Result) {
- val permissions = ArrayList()
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
- permissions.add(Manifest.permission.BLUETOOTH_SCAN)
+ private fun isBluetoothEnabled(): Boolean {
+ return bluetoothAdapter?.isEnabled ?: false
}
- permissionManager.ensurePermissions(permissions.toTypedArray()) { success: Boolean, deniedPermissions: List? ->
- run {
- if (success) {
- result.success(bluetoothAdapter?.isDiscovering ?: false)
- } else {
- result.error(
- PermissionManager.ERROR_PERMISSION_DENIED,
- String.format(
- "Required permission(s) %s denied",
- deniedPermissions?.joinToString() ?: ""
- ), null
- )
+ private fun turnOn(result: Result) {
+ if (isBluetoothEnabled()) {
+ result.success(null)
+ return
+ }
+ val permissions = ArrayList()
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
+ permissions.add(Manifest.permission.BLUETOOTH_CONNECT)
+ }
+ permissionManager.ensurePermissions(permissions.toTypedArray()) { success: Boolean, deniedPermissions: List? ->
+ run {
+ try {
+ if (success) {
+ val enableBtIntent = Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE)
+ startActivityForResult(
+ activityPluginBinding!!.activity,
+ enableBtIntent,
+ PermissionManager.REQUEST_ENABLE_BT,
+ null
+ )
+ result.success(null)
+ return@run
+ }
+ } catch (_: Exception) {
+ }
+ result.error(
+ PermissionManager.ERROR_PERMISSION_DENIED,
+ String.format(
+ "Required permission(s) %s denied",
+ deniedPermissions?.joinToString() ?: ""
+ ), null
+ )
+
+ }
}
- }
- }
- }
-
- @SuppressLint("MissingPermission")
- private fun bondDevice(result: Result, address: String) {
- if (!BluetoothAdapter.checkBluetoothAddress(address)) {
- result.error(
- BlueClassicHelper.ERROR_ADDRESS_INVALID,
- "The bluetooth address $address is invalid",
- null
- )
- return
}
- val permissions = ArrayList()
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
- permissions.add(Manifest.permission.BLUETOOTH_CONNECT)
+ private fun getAdapterState(): String {
+ return bluetoothAdapter?.state?.let { BlueClassicHelper.adapterStateString(it) }
+ ?: "unavailable"
}
- permissionManager.ensurePermissions(permissions.toTypedArray()) { success: Boolean, deniedPermissions: List? ->
- run {
- if (success) {
- val device = bluetoothAdapter?.getRemoteDevice(address)
- result.success(device?.createBond() ?: false)
- } else {
- result.error(
- PermissionManager.ERROR_PERMISSION_DENIED,
- String.format(
- "Required permission(s) %s denied",
- deniedPermissions?.joinToString() ?: ""
- ), null
- )
+ @SuppressLint("MissingPermission")
+ private fun getBondedDevices(result: Result) {
+ val permissions = ArrayList()
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
+ permissions.add(Manifest.permission.BLUETOOTH_CONNECT)
+ }
+ permissionManager.ensurePermissions(permissions.toTypedArray()) { success: Boolean, deniedPermissions: List? ->
+ run {
+ if (success) {
+ val devices: List>? =
+ bluetoothAdapter?.bondedDevices?.filter { it.type != BluetoothDevice.DEVICE_TYPE_LE }
+ ?.map {
+ BlueClassicHelper.bluetoothDeviceToMap(it)
+ }
+
+ result.success(devices)
+ } else {
+ result.error(
+ PermissionManager.ERROR_PERMISSION_DENIED,
+ String.format(
+ "Required permission(s) %s denied",
+ deniedPermissions?.joinToString() ?: ""
+ ), null
+ )
+ }
+ }
}
- }
}
- }
+ @SuppressLint("MissingPermission")
+ private fun startScan(result: Result, usesFineLocation: Boolean) {
+ val permissions = ArrayList()
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
+ permissions.add(Manifest.permission.BLUETOOTH_SCAN)
+ permissions.add(Manifest.permission.BLUETOOTH_CONNECT)
+ }
+ if (usesFineLocation) {
+ permissions.add(Manifest.permission.ACCESS_FINE_LOCATION)
+ }
- @SuppressLint("MissingPermission")
- private fun connect(result: Result, address: String) {
- var permissionSuccess = true
- val permissions = ArrayList()
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
- permissions.add(Manifest.permission.BLUETOOTH_CONNECT)
- permissions.add(Manifest.permission.BLUETOOTH_SCAN)
+ permissionManager.ensurePermissions(permissions.toTypedArray()) { success: Boolean, deniedPermissions: List? ->
+ run {
+ if (success) {
+ val discoveryStartState = bluetoothAdapter?.startDiscovery()
+ result.success(discoveryStartState)
+ } else {
+ result.error(
+ PermissionManager.ERROR_PERMISSION_DENIED,
+ String.format(
+ "Required permission(s) %s denied",
+ deniedPermissions?.joinToString() ?: ""
+ ), null
+ )
+ }
+ }
+ }
}
- permissionManager.ensurePermissions(permissions.toTypedArray()){ success: Boolean, deniedPermissions: List? ->
- if(!success){
- result.error(
- PermissionManager.ERROR_PERMISSION_DENIED,
- String.format(
- "Required permission(s) %s denied",
- deniedPermissions?.joinToString() ?: ""
- ), null
- )
- }
- permissionSuccess = success
+ @SuppressLint("MissingPermission")
+ private fun stopScan(result: Result) {
+ val permissions = ArrayList()
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
+ permissions.add(Manifest.permission.BLUETOOTH_SCAN)
+ }
+
+ permissionManager.ensurePermissions(permissions.toTypedArray()) { success: Boolean, deniedPermissions: List? ->
+ run {
+ if (success) {
+ result.success(bluetoothAdapter?.cancelDiscovery())
+ } else {
+ result.error(
+ PermissionManager.ERROR_PERMISSION_DENIED,
+ String.format(
+ "Required permission(s) %s denied",
+ deniedPermissions?.joinToString() ?: ""
+ ), null
+ )
+ }
+ }
+ }
}
- if(!permissionSuccess) return
+ @SuppressLint("MissingPermission")
+ private fun isScanningNow(result: Result) {
+ val permissions = ArrayList()
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
+ permissions.add(Manifest.permission.BLUETOOTH_SCAN)
+ }
- if (!BluetoothAdapter.checkBluetoothAddress(address)) {
- result.error(
- BlueClassicHelper.ERROR_ADDRESS_INVALID,
- "The bluetooth address $address is invalid",
- null
- )
- return
+ permissionManager.ensurePermissions(permissions.toTypedArray()) { success: Boolean, deniedPermissions: List? ->
+ run {
+ if (success) {
+ result.success(bluetoothAdapter?.isDiscovering ?: false)
+ } else {
+ result.error(
+ PermissionManager.ERROR_PERMISSION_DENIED,
+ String.format(
+ "Required permission(s) %s denied",
+ deniedPermissions?.joinToString() ?: ""
+ ), null
+ )
+ }
+ }
+ }
}
- val id = ++lastConnectionId
- val connection = BluetoothConnectionWrapper(id, bluetoothAdapter!!)
- connections.put(id, connection)
- Log.d(
- TAG,
- "Connecting to $address (id: $id)"
- )
-
- Executors.newSingleThreadExecutor().execute {
- Handler(Looper.getMainLooper()).post {
- try {
- connection.connect(address)
- activityPluginBinding!!.activity.runOnUiThread {
- result.success(id)
- }
- } catch (ex: java.lang.Exception) {
- activityPluginBinding!!.activity.runOnUiThread {
+ @SuppressLint("MissingPermission")
+ private fun bondDevice(result: Result, address: String) {
+ if (!BluetoothAdapter.checkBluetoothAddress(address)) {
result.error(
- "connect_error",
- ex.message, null
+ BlueClassicHelper.ERROR_ADDRESS_INVALID,
+ "The bluetooth address $address is invalid",
+ null
)
- }
- connections.remove(id)
+ return
}
- }
- }
- }
-
- private fun write(result: Result, id: Int, bytes: ByteArray) {
- val connection: BluetoothConnection = connections[id]
- Executors.newSingleThreadExecutor().execute {
- Handler(Looper.getMainLooper()).post {
- connection.write(bytes)
- activityPluginBinding!!.activity.runOnUiThread {
- result.success(null)
+
+ val permissions = ArrayList()
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
+ permissions.add(Manifest.permission.BLUETOOTH_CONNECT)
+ }
+
+ permissionManager.ensurePermissions(permissions.toTypedArray()) { success: Boolean, deniedPermissions: List? ->
+ run {
+ if (success) {
+ val device = bluetoothAdapter?.getRemoteDevice(address)
+ result.success(device?.createBond() ?: false)
+ } else {
+ result.error(
+ PermissionManager.ERROR_PERMISSION_DENIED,
+ String.format(
+ "Required permission(s) %s denied",
+ deniedPermissions?.joinToString() ?: ""
+ ), null
+ )
+ }
+ }
}
- }
}
- }
- // ------ INNER CLASS ------
+ @SuppressLint("MissingPermission")
+ private fun connect(result: Result, address: String) {
+ var permissionSuccess = true
+ val permissions = ArrayList()
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
+ permissions.add(Manifest.permission.BLUETOOTH_CONNECT)
+ permissions.add(Manifest.permission.BLUETOOTH_SCAN)
+ }
- private inner class BluetoothConnectionWrapper(private val id: Int, adapter: BluetoothAdapter) :
- BluetoothConnection(adapter), EventChannel.StreamHandler {
- private var readSink: EventSink? = null
- private var readChannel: EventChannel = EventChannel(
- binaryMessenger,
- BlueClassicHelper.NAMESPACE + "/connection/" + id
- )
+ permissionManager.ensurePermissions(permissions.toTypedArray()) { success: Boolean, deniedPermissions: List? ->
+ if (!success) {
+ result.error(
+ PermissionManager.ERROR_PERMISSION_DENIED,
+ String.format(
+ "Required permission(s) %s denied",
+ deniedPermissions?.joinToString() ?: ""
+ ), null
+ )
+ }
+ permissionSuccess = success
+ }
- init {
- readChannel.setStreamHandler(this)
- }
+ if (!permissionSuccess) return
- override fun onRead(data: ByteArray?) {
- activityPluginBinding?.activity?.runOnUiThread {
- if (readSink != null) {
- readSink?.success(data)
+ if (!BluetoothAdapter.checkBluetoothAddress(address)) {
+ result.error(
+ BlueClassicHelper.ERROR_ADDRESS_INVALID,
+ "The bluetooth address $address is invalid",
+ null
+ )
+ return
}
- }
- }
- override fun onDisconnected(byRemote: Boolean) {
- activityPluginBinding?.activity?.runOnUiThread {
- if (byRemote) {
- Log.d(
+ val id = ++lastConnectionId
+ val connection = BluetoothConnectionWrapper(id, bluetoothAdapter!!)
+ connections.put(id, connection)
+ Log.d(
TAG,
- "onDisconnected by remote (id: $id)"
- )
- if (readSink != null) {
- readSink?.endOfStream()
- readSink = null
- }
- } else {
- Log.d(
- TAG,
- "onDisconnected by local (id: $id)"
- )
+ "Connecting to $address (id: $id)"
+ )
+
+ Executors.newSingleThreadExecutor().execute {
+ Handler(Looper.getMainLooper()).post {
+ try {
+ connection.connect(address)
+ activityPluginBinding!!.activity.runOnUiThread {
+ result.success(id)
+ }
+ } catch (ex: java.lang.Exception) {
+ activityPluginBinding!!.activity.runOnUiThread {
+ result.error(
+ "connect_error",
+ ex.message, null
+ )
+ }
+ connections.remove(id)
+ }
+ }
}
- }
}
- override fun onListen(obj: Any?, eventSink: EventSink) {
- readSink = eventSink
+ private fun write(result: Result, id: Int, bytes: ByteArray) {
+ val connection: BluetoothConnection = connections[id]
+ Executors.newSingleThreadExecutor().execute {
+ Handler(Looper.getMainLooper()).post {
+ connection.write(bytes)
+ activityPluginBinding!!.activity.runOnUiThread {
+ result.success(null)
+ }
+ }
+ }
+
}
- override fun onCancel(obj: Any?) {
- this.disconnect()
+ // ------ INNER CLASS ------
- Executors.newSingleThreadExecutor().execute {
- Handler(Looper.getMainLooper()).post {
- readChannel.setStreamHandler(null)
- connections.remove(id)
- Log.d(
- TAG,
- "Disconnected (id: $id)"
- )
+ private inner class BluetoothConnectionWrapper(private val id: Int, adapter: BluetoothAdapter) :
+ BluetoothConnection(adapter), EventChannel.StreamHandler {
+ private var readSink: EventSink? = null
+ private var readChannel: EventChannel = EventChannel(
+ binaryMessenger,
+ BlueClassicHelper.NAMESPACE + "/connection/" + id
+ )
+
+ init {
+ readChannel.setStreamHandler(this)
+ }
+
+ override fun onRead(data: ByteArray?) {
+ activityPluginBinding?.activity?.runOnUiThread {
+ if (readSink != null) {
+ readSink?.success(data)
+ }
+ }
+ }
+
+ override fun onDisconnected(byRemote: Boolean) {
+ activityPluginBinding?.activity?.runOnUiThread {
+ if (byRemote) {
+ Log.d(
+ TAG,
+ "onDisconnected by remote (id: $id)"
+ )
+ if (readSink != null) {
+ readSink?.endOfStream()
+ readSink = null
+ }
+ } else {
+ Log.d(
+ TAG,
+ "onDisconnected by local (id: $id)"
+ )
+ }
+ }
+ }
+
+ override fun onListen(obj: Any?, eventSink: EventSink) {
+ readSink = eventSink
+ }
+
+ override fun onCancel(obj: Any?) {
+ this.disconnect()
+
+ Executors.newSingleThreadExecutor().execute {
+ Handler(Looper.getMainLooper()).post {
+ readChannel.setStreamHandler(null)
+ connections.remove(id)
+ Log.d(
+ TAG,
+ "Disconnected (id: $id)"
+ )
+ }
+ }
}
- }
}
- }
}
diff --git a/android/src/main/kotlin/dev/lenhart/flutter_blue_classic/PermissionManager.kt b/android/src/main/kotlin/dev/lenhart/flutter_blue_classic/PermissionManager.kt
index a114f24..4301794 100644
--- a/android/src/main/kotlin/dev/lenhart/flutter_blue_classic/PermissionManager.kt
+++ b/android/src/main/kotlin/dev/lenhart/flutter_blue_classic/PermissionManager.kt
@@ -11,10 +11,10 @@ import io.flutter.plugin.common.PluginRegistry
class PermissionManager(private var context: Context, private var activity: Activity?) :
PluginRegistry.RequestPermissionsResultListener {
- companion object{
- const val ERROR_PERMISSION_DENIED = "permissionDenied"
- const val REQUEST_ENABLE_BT = 1337
- }
+ companion object {
+ const val ERROR_PERMISSION_DENIED = "permissionDenied"
+ const val REQUEST_ENABLE_BT = 1337
+ }
private var lastPermissionRequestCode = 1
private val callbackForRequestCode = HashMap) -> Unit)>()
@@ -32,8 +32,10 @@ class PermissionManager(private var context: Context, private var activity: Acti
): Boolean {
if (requestCode < lastPermissionRequestCode) {
- val success = grantResults.isNotEmpty() && grantResults.all { it == PackageManager.PERMISSION_GRANTED }
- val deniedPermissions = permissions.zip(grantResults.asIterable()).filter { it.second == PackageManager.PERMISSION_DENIED }.map { it.first }
+ val success =
+ grantResults.isNotEmpty() && grantResults.all { it == PackageManager.PERMISSION_GRANTED }
+ val deniedPermissions = permissions.zip(grantResults.asIterable())
+ .filter { it.second == PackageManager.PERMISSION_DENIED }.map { it.first }
val callback = callbackForRequestCode.remove(requestCode)
callback?.invoke(success, deniedPermissions)
@@ -42,7 +44,10 @@ class PermissionManager(private var context: Context, private var activity: Acti
return false
}
- fun ensurePermissions(permissions: Array, callback: ((Boolean, List?) -> Unit)){
+ fun ensurePermissions(
+ permissions: Array,
+ callback: ((Boolean, List?) -> Unit)
+ ) {
val requestCode: Int = lastPermissionRequestCode
lastPermissionRequestCode++
callbackForRequestCode[requestCode] = callback
@@ -55,7 +60,7 @@ class PermissionManager(private var context: Context, private var activity: Acti
}
}
- if(permissionsNeeded.isEmpty()){
+ if (permissionsNeeded.isEmpty()) {
callback.invoke(true, null)
return
}
@@ -63,14 +68,15 @@ class PermissionManager(private var context: Context, private var activity: Acti
requestPermissions(requestCode, permissionsNeeded.toTypedArray())
}
- private fun requestPermissions(requestCode: Int, permissions: Array){
+ private fun requestPermissions(requestCode: Int, permissions: Array) {
pendingRequestCount = permissions.size
activity?.let {
ActivityCompat.requestPermissions(
it,
permissions,
- requestCode)
+ requestCode
+ )
}
}
diff --git a/android/src/main/kotlin/dev/lenhart/flutter_blue_classic/ScanResultReceiver.kt b/android/src/main/kotlin/dev/lenhart/flutter_blue_classic/ScanResultReceiver.kt
index 56ed513..592338c 100644
--- a/android/src/main/kotlin/dev/lenhart/flutter_blue_classic/ScanResultReceiver.kt
+++ b/android/src/main/kotlin/dev/lenhart/flutter_blue_classic/ScanResultReceiver.kt
@@ -1,5 +1,6 @@
package dev.lenhart.flutter_blue_classic
+import android.annotation.SuppressLint
import android.bluetooth.BluetoothDevice
import android.content.BroadcastReceiver
import android.content.Context
@@ -7,40 +8,46 @@ import android.content.Intent
import android.os.Build
import io.flutter.plugin.common.EventChannel
-class ScanResultReceiver : EventChannel.StreamHandler{
+class ScanResultReceiver : EventChannel.StreamHandler {
- companion object{
- const val CHANNEL_NAME: String = "${BlueClassicHelper.NAMESPACE}/scanResults"
- }
+ companion object {
+ const val CHANNEL_NAME: String = "${BlueClassicHelper.NAMESPACE}/scanResults"
+ }
- private var adapterStateEventSink: EventChannel.EventSink? = null
+ private var adapterStateEventSink: EventChannel.EventSink? = null
- override fun onListen(arguments: Any?, events: EventChannel.EventSink?) {
- adapterStateEventSink = events
- }
+ override fun onListen(arguments: Any?, events: EventChannel.EventSink?) {
+ adapterStateEventSink = events
+ }
- override fun onCancel(arguments: Any?) {
- adapterStateEventSink = null
- }
+ override fun onCancel(arguments: Any?) {
+ adapterStateEventSink = null
+ }
- val scanResultReceiver: BroadcastReceiver = object : BroadcastReceiver() {
- override fun onReceive(context: Context, intent: Intent) {
- val device: BluetoothDevice? = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
- intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE, BluetoothDevice::class.java)
+ val scanResultReceiver: BroadcastReceiver = object : BroadcastReceiver() {
+ @SuppressLint("MissingPermission")
+ override fun onReceive(context: Context, intent: Intent) {
+ val device: BluetoothDevice? =
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
+ intent.getParcelableExtra(
+ BluetoothDevice.EXTRA_DEVICE,
+ BluetoothDevice::class.java
+ )
} else {
intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE)
}
- if (device != null) {
- adapterStateEventSink.let { it?.success(
+ val rssi = intent.getShortExtra(BluetoothDevice.EXTRA_RSSI, Short.MIN_VALUE)
+
+ if (device != null && device.type != BluetoothDevice.DEVICE_TYPE_LE) {
+ adapterStateEventSink.let {
+ it?.success(
BlueClassicHelper.bluetoothDeviceToMap(
- device
+ device, rssi
)
- ) }
-
+ )
}
-
-
}
}
+ }
}
\ No newline at end of file
diff --git a/example/lib/device_screen.dart b/example/lib/device_screen.dart
new file mode 100644
index 0000000..d76e97d
--- /dev/null
+++ b/example/lib/device_screen.dart
@@ -0,0 +1,69 @@
+import 'dart:async';
+import 'dart:convert';
+
+import 'package:flutter/foundation.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter_blue_classic/flutter_blue_classic.dart';
+
+class DeviceScreen extends StatefulWidget {
+ const DeviceScreen({super.key, required this.connection});
+
+ final BluetoothConnection connection;
+
+ @override
+ State createState() => _DeviceScreenState();
+}
+
+class _DeviceScreenState extends State {
+ StreamSubscription? _readSubscription;
+ final List _receivedInput = [];
+
+ @override
+ void initState() {
+ _readSubscription = widget.connection.input?.listen((event) {
+ if (mounted) {
+ setState(() => _receivedInput.add(utf8.decode(event)));
+ }
+ });
+ super.initState();
+ }
+
+ @override
+ void dispose() {
+ widget.connection.dispose();
+ _readSubscription?.cancel();
+ super.dispose();
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ return Scaffold(
+ appBar: AppBar(
+ title: Text("Connection to ${widget.connection.address}"),
+ ),
+ body: ListView(
+ children: [
+ ElevatedButton(
+ onPressed: () {
+ try {
+ widget.connection.writeString("hello world");
+ } catch (e) {
+ if (kDebugMode) print(e);
+ ScaffoldMessenger.maybeOf(context)?.showSnackBar(SnackBar(
+ content: Text(
+ "Error sending to device. Device is ${widget.connection.isConnected ? "connected" : "not connected"}")));
+ }
+ },
+ child: const Text("Send hello world to remote device")),
+ const Divider(),
+ Padding(
+ padding: const EdgeInsets.symmetric(vertical: 8),
+ child: Text("Received data",
+ style: Theme.of(context).textTheme.titleLarge),
+ ),
+ for (String input in _receivedInput) Text(input),
+ ],
+ ),
+ );
+ }
+}
diff --git a/example/lib/main.dart b/example/lib/main.dart
index c98450b..4c7731d 100644
--- a/example/lib/main.dart
+++ b/example/lib/main.dart
@@ -3,19 +3,29 @@ import 'dart:async';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_blue_classic/flutter_blue_classic.dart';
+import 'package:flutter_blue_classic_example/device_screen.dart';
void main() {
runApp(const MyApp());
}
-class MyApp extends StatefulWidget {
+class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
- State createState() => _MyAppState();
+ Widget build(BuildContext context) {
+ return const MaterialApp(home: MainScreen());
+ }
+}
+
+class MainScreen extends StatefulWidget {
+ const MainScreen({super.key});
+
+ @override
+ State createState() => _MainScreenState();
}
-class _MyAppState extends State {
+class _MainScreenState extends State {
final _flutterBlueClassicPlugin = FlutterBlueClassic();
BluetoothAdapterState _adapterState = BluetoothAdapterState.unknown;
@@ -38,13 +48,16 @@ class _MyAppState extends State {
try {
adapterState = await _flutterBlueClassicPlugin.adapterStateNow;
- _adapterStateSubscription = _flutterBlueClassicPlugin.adapterState.listen((current) {
+ _adapterStateSubscription =
+ _flutterBlueClassicPlugin.adapterState.listen((current) {
if (mounted) setState(() => _adapterState = current);
});
- _scanSubscription = _flutterBlueClassicPlugin.scanResults.listen((device) {
+ _scanSubscription =
+ _flutterBlueClassicPlugin.scanResults.listen((device) {
if (mounted) setState(() => _scanResults.add(device));
});
- _scanningStateSubscription = _flutterBlueClassicPlugin.isScanning.listen((isScanning) {
+ _scanningStateSubscription =
+ _flutterBlueClassicPlugin.isScanning.listen((isScanning) {
if (mounted) setState(() => _isScanning = isScanning);
});
} catch (e) {
@@ -70,41 +83,64 @@ class _MyAppState extends State {
Widget build(BuildContext context) {
List scanResults = _scanResults.toList();
- return MaterialApp(
- home: Scaffold(
- appBar: AppBar(
- title: const Text('FlutterBluePlus example app'),
- ),
- body: ListView(
- children: [
- ListTile(
- title: const Text("Bluetooth Adapter state"),
- subtitle: const Text("Tap to enable"),
- trailing: Text(_adapterState.name),
- leading: const Icon(Icons.settings_bluetooth),
- onTap: () => _flutterBlueClassicPlugin.turnOn(),
- ),
- const Divider(),
- if (scanResults.isEmpty)
- const Center(child: Text("No devices found yet"))
- else
- for (var result in scanResults)
- ListTile(
- title: Text("${result.name ?? "???"} (${result.address})"),
- )
- ],
- ),
- floatingActionButton: FloatingActionButton.extended(
- onPressed: () {
- if (_isScanning) {
- _flutterBlueClassicPlugin.stopScan();
- } else {
- _flutterBlueClassicPlugin.startScan();
- }
- },
- label: Text(_isScanning ? "Scanning..." : "Start device scan"),
- icon: Icon(_isScanning ? Icons.bluetooth_searching : Icons.bluetooth),
- ),
+ return Scaffold(
+ appBar: AppBar(
+ title: const Text('FlutterBluePlus example app'),
+ ),
+ body: ListView(
+ children: [
+ ListTile(
+ title: const Text("Bluetooth Adapter state"),
+ subtitle: const Text("Tap to enable"),
+ trailing: Text(_adapterState.name),
+ leading: const Icon(Icons.settings_bluetooth),
+ onTap: () => _flutterBlueClassicPlugin.turnOn(),
+ ),
+ const Divider(),
+ if (scanResults.isEmpty)
+ const Center(child: Text("No devices found yet"))
+ else
+ for (var result in scanResults)
+ ListTile(
+ title: Text("${result.name ?? "???"} (${result.address})"),
+ subtitle: Text(
+ "Bondstate: ${result.bondState.name}, Device type: ${result.type.name}"),
+ trailing: Text("${result.rssi} dBm"),
+ onTap: () async {
+ BluetoothConnection? connection;
+ try {
+ connection =
+ await _flutterBlueClassicPlugin.connect(result.address);
+ if (!this.context.mounted) return;
+ if (connection != null && connection.isConnected) {
+ Navigator.push(
+ context,
+ MaterialPageRoute(
+ builder: (context) =>
+ DeviceScreen(connection: connection!)));
+ }
+ } catch (e) {
+ if (kDebugMode) print(e);
+ connection?.dispose();
+ ScaffoldMessenger.maybeOf(context)?.showSnackBar(
+ const SnackBar(
+ content: Text("Error connecting to device")));
+ }
+ },
+ )
+ ],
+ ),
+ floatingActionButton: FloatingActionButton.extended(
+ onPressed: () {
+ if (_isScanning) {
+ _flutterBlueClassicPlugin.stopScan();
+ } else {
+ _scanResults.clear();
+ _flutterBlueClassicPlugin.startScan();
+ }
+ },
+ label: Text(_isScanning ? "Scanning..." : "Start device scan"),
+ icon: Icon(_isScanning ? Icons.bluetooth_searching : Icons.bluetooth),
),
);
}
diff --git a/lib/src/flutter_blue_classic.dart b/lib/src/flutter_blue_classic.dart
index 9790300..3bdd3b6 100644
--- a/lib/src/flutter_blue_classic.dart
+++ b/lib/src/flutter_blue_classic.dart
@@ -5,7 +5,7 @@ import 'model/bluetooth_device.dart';
class FlutterBlueClassic {
final _instance = FlutterBlueClassicPlatform.instance;
- /// Indicates whether your app is using the fine location feature on android. For iOS apps this can be ignored.
+ /// Indicates whether your app is using the fine location feature.
///
/// Note that you have to either define the ACCESS_FINE_LOCATION permission and set this [_usesFineLocation] to true or the
/// usesPermissionFlags="neverForLocation" on the relevant manifest tag and set this to false
@@ -20,13 +20,13 @@ class FlutterBlueClassic {
Future get isEnabled => _instance.isEnabled();
/// Returns the current adapter state
- Future get adapterStateNow => _instance.adapterStateNow();
+ Future get adapterStateNow =>
+ _instance.adapterStateNow();
/// Returns an event stream for all adapter state changes
Stream get adapterState => _instance.adapterState();
- /// On Android this returns the list of bonded devices.
- /// On iOS/macOS this will always return null
+ /// Returns the list of bonded devices.
Future?> get bondedDevices => _instance.bondedDevices();
/// This will attempt to start scanning for Bluetooth devices.
@@ -44,21 +44,21 @@ class FlutterBlueClassic {
/// Returns an event stream for every bluetooth device found during scan
Stream get scanResults => _instance.scanResults();
- /// On Android this will attempt to enable Bluetooth. On iOS this is a no-op.
+ /// Requests to turns the bluetooth adapter on.
void turnOn() => _instance.turnOn();
- /// On Android this creates a bond to the device with the given address.
- /// On iOS/macOS this is a no-op and will always return false
+ /// Tries to create a bond to the device with the given address.
///
/// Returns whether the bond was successfully created.
Future bondDevice(String address) => _instance.bondDevice(address);
- /// Creates a connection to the device with the given address.
- Future connect(String address) => _instance.connect(address);
+ /// Tries to create a connection to the device with the given address.
+ Future connect(String address) =>
+ _instance.connect(address);
}
/// State of the Bluetooth adapter
-enum BluetoothAdapterState { unknown, unavailable, unauthorized, turningOn, on, turningOff, off }
+enum BluetoothAdapterState { unknown, turningOn, on, turningOff, off }
/// Bonding state on a device
enum BluetoothBondState { none, bonding, bonded }
diff --git a/lib/src/flutter_blue_classic_method_channel.dart b/lib/src/flutter_blue_classic_method_channel.dart
index 95edc3c..97d1ffc 100644
--- a/lib/src/flutter_blue_classic_method_channel.dart
+++ b/lib/src/flutter_blue_classic_method_channel.dart
@@ -18,32 +18,39 @@ class MethodChannelFlutterBlueClassic extends FlutterBlueClassicPlatform {
/// The event channel used to get updates for the adapter state
@visibleForTesting
- final EventChannel adapterStateEventChannel = const EventChannel("$namespace/adapterState");
+ final EventChannel adapterStateEventChannel =
+ const EventChannel("$namespace/adapterState");
/// The event channel used to get updates for new discovered devices
@visibleForTesting
- final EventChannel scanStateEventChannel = const EventChannel("$namespace/discoveryState");
+ final EventChannel scanStateEventChannel =
+ const EventChannel("$namespace/discoveryState");
/// The event channel used to get updates for new discovered devices
@visibleForTesting
- final EventChannel scanResultEventChannel = const EventChannel("$namespace/scanResults");
+ final EventChannel scanResultEventChannel =
+ const EventChannel("$namespace/scanResults");
@override
- Future isSupported() async => await methodChannel.invokeMethod("isSupported") ?? false;
+ Future isSupported() async =>
+ await methodChannel.invokeMethod("isSupported") ?? false;
@override
- Future isEnabled() async => await methodChannel.invokeMethod("isEnabled") ?? false;
+ Future isEnabled() async =>
+ await methodChannel.invokeMethod("isEnabled") ?? false;
@override
Future adapterStateNow() async {
String? state = await methodChannel.invokeMethod("getAdapterState");
- return BluetoothAdapterState.values.firstWhere((e) => e.name == state, orElse: () => BluetoothAdapterState.unknown);
+ return BluetoothAdapterState.values.firstWhere((e) => e.name == state,
+ orElse: () => BluetoothAdapterState.unknown);
}
@override
Stream adapterState() {
return adapterStateEventChannel.receiveBroadcastStream().map((event) =>
- BluetoothAdapterState.values.firstWhere((e) => e.name == event, orElse: () => BluetoothAdapterState.unknown));
+ BluetoothAdapterState.values.firstWhere((e) => e.name == event,
+ orElse: () => BluetoothAdapterState.unknown));
}
@override
@@ -57,15 +64,20 @@ class MethodChannelFlutterBlueClassic extends FlutterBlueClassicPlatform {
@override
Stream isScanning() {
- return scanStateEventChannel.receiveBroadcastStream().map((event) => event == true ? true : false);
+ return scanStateEventChannel
+ .receiveBroadcastStream()
+ .map((event) => event == true ? true : false);
}
@override
- Future isScanningNow() async => await methodChannel.invokeMethod("isScanningNow") ?? false;
+ Future isScanningNow() async =>
+ await methodChannel.invokeMethod("isScanningNow") ?? false;
@override
Stream scanResults() {
- return scanResultEventChannel.receiveBroadcastStream().map((event) => BluetoothDevice.fromMap(event));
+ return scanResultEventChannel
+ .receiveBroadcastStream()
+ .map((event) => BluetoothDevice.fromMap(event));
}
@override
@@ -75,7 +87,8 @@ class MethodChannelFlutterBlueClassic extends FlutterBlueClassicPlatform {
@override
void startScan(bool usesFineLocation) {
- methodChannel.invokeMethod("startScan", {"usesFineLocation": usesFineLocation});
+ methodChannel
+ .invokeMethod("startScan", {"usesFineLocation": usesFineLocation});
}
@override
@@ -84,13 +97,19 @@ class MethodChannelFlutterBlueClassic extends FlutterBlueClassicPlatform {
}
@override
- Future bondDevice(String address) async =>
- Platform.isAndroid ? await methodChannel.invokeMethod("bondDevice", {"address": address}) ?? false : false;
+ Future bondDevice(String address) async => Platform.isAndroid
+ ? await methodChannel
+ .invokeMethod("bondDevice", {"address": address}) ??
+ false
+ : false;
@override
Future connect(String address) async {
- int? id = await methodChannel.invokeMethod("connect", {"address": address});
- return id != null ? BluetoothConnection.fromConnectionId(id, address) : null;
+ int? id =
+ await methodChannel.invokeMethod("connect", {"address": address});
+ return id != null
+ ? BluetoothConnection.fromConnectionId(id, address)
+ : null;
}
@override
diff --git a/lib/src/flutter_blue_classic_platform_interface.dart b/lib/src/flutter_blue_classic_platform_interface.dart
index ab8c358..433751a 100644
--- a/lib/src/flutter_blue_classic_platform_interface.dart
+++ b/lib/src/flutter_blue_classic_platform_interface.dart
@@ -13,7 +13,8 @@ abstract class FlutterBlueClassicPlatform extends PlatformInterface {
static final Object _token = Object();
- static FlutterBlueClassicPlatform _instance = MethodChannelFlutterBlueClassic();
+ static FlutterBlueClassicPlatform _instance =
+ MethodChannelFlutterBlueClassic();
/// The default instance of [BlueClassicPlatform] to use.
///
diff --git a/lib/src/model/bluetooth_connection.dart b/lib/src/model/bluetooth_connection.dart
index b1c0513..e0701a4 100644
--- a/lib/src/model/bluetooth_connection.dart
+++ b/lib/src/model/bluetooth_connection.dart
@@ -10,14 +10,16 @@ import '../flutter_blue_classic_platform_interface.dart';
/// Represents an ongoing Bluetooth connection to a remote device.
class BluetoothConnection {
BluetoothConnection.fromConnectionId(this._id, this.address)
- : _readChannel = EventChannel("${MethodChannelFlutterBlueClassic.namespace}/connection/$_id") {
+ : _readChannel = EventChannel(
+ "${MethodChannelFlutterBlueClassic.namespace}/connection/$_id") {
_readStreamController = StreamController();
- _readStreamSubscription = _readChannel.receiveBroadcastStream().cast().listen(
- _readStreamController.add,
- onError: _readStreamController.addError,
- onDone: close,
- );
+ _readStreamSubscription =
+ _readChannel.receiveBroadcastStream().cast().listen(
+ _readStreamController.add,
+ onError: _readStreamController.addError,
+ onDone: close,
+ );
input = _readStreamController.stream;
output = BluetoothStreamSink(_id);
@@ -25,6 +27,8 @@ class BluetoothConnection {
/// This ID identifies the real BluetoothConenction object on platform side code.
final int _id;
+
+ /// The Bluetooth-Adress of the remote device.
final String address;
final EventChannel _readChannel;
@@ -59,7 +63,9 @@ class BluetoothConnection {
return Future.wait([
output.close(),
_readStreamSubscription.cancel(),
- (!_readStreamController.isClosed) ? _readStreamController.close() : Future.value()
+ (!_readStreamController.isClosed)
+ ? _readStreamController.close()
+ : Future.value()
], eagerError: true);
}
@@ -71,7 +77,8 @@ class BluetoothConnection {
}
/// Helper class for sending data.
-class BluetoothStreamSink implements EventSink, StreamConsumer {
+class BluetoothStreamSink
+ implements EventSink, StreamConsumer {
final int _id;
final _instance = FlutterBlueClassicPlatform.instance;
@@ -115,7 +122,8 @@ class BluetoothStreamSink implements EventSink, StreamConsumer BluetoothDevice(
+ BluetoothDevice._(
+ {required this.address,
+ this.name = "",
+ this.alias,
+ this.type = BluetoothDeviceType.unknown,
+ this.rssi,
+ this.bondState = BluetoothBondState.none});
+ factory BluetoothDevice.fromMap(Map map) => BluetoothDevice._(
name: map["name"],
+ alias: map["alias"],
address: map["address"]!,
- bondState: BluetoothBondState.values
- .firstWhere((e) => e.name == map["bondState"], orElse: () => BluetoothBondState.none));
+ type: BluetoothDeviceType.values.firstWhere(
+ (e) => e.name == map["deviceType"],
+ orElse: () => BluetoothDeviceType.unknown),
+ rssi: map["rssi"],
+ bondState: BluetoothBondState.values.firstWhere(
+ (e) => e.name == map["bondState"],
+ orElse: () => BluetoothBondState.none));
@override
operator ==(Object other) {
@@ -24,3 +48,5 @@ class BluetoothDevice {
@override
int get hashCode => address.hashCode;
}
+
+enum BluetoothDeviceType { classic, dual, unknown }