Kotlin Bluetooth

Native Android ==> Bluetooth module

The Validic SDK introduces a Kotlin API for interacting with BluetoothPeripherals and BluetoothDevices. All BluetoothRequests are executed using kotlin coroutines and are exposed as suspend functions.

Scanning

viewLifecycleScope.launch {  
  val request = ScanRequest(bluetoothPeripheral)  
  request.setOnStartHandler { Log.i("Bluetooth", "Starting scanning") }  
  request.setOnCompleteHandler { Log.i("Bluetooth", "Scanning completed" }  
  try {  
    val scanResult: BluetoothScanResult = request.enqueue()  
  } catch (e: ValidicBluetoothException) {  
    Log.w("Bluetooth", e.getMessage())  
  }  
}

After retrieving instances of a BluetoothPeripheral and BluetoothDevice from a BluetoothScanResult operations with a BluetoothPeripheral and BluetoothDevice are possible.

Pairing

viewLifecycleScope.launch {  
  val request = ScanRequest(bluetoothPeripheral)  
  request.onStart = object : OnStartHandler {  
    override fun onStart() {  
      Log.i("Bluetooth", "Scanning starting")  
    }  
  }  
  request.onCompleted = object : OnCompleteHandler {  
    override fun onComplete{  
      Log.i("Bluetooth", "Scanning completed")  
    }  
  }  
  try {  
    val scanResult: BluetoothScanResult = request.enqueue()  
    val readRequest: ReadRequest =  
      PairRequest(scanResult.bluetoothPeripheral, scanResult.bluetoothDevice)  
    readRequest.onStart = object : OnStartHandler {  
      override fun onStart() {  
        Log.i("Bluetooth", "Pairing starting")  
      }  
    }  
    readRequest.onCompleted = object : OnCompleteHandler {  
      override fun onComplete{  
        Log.i("Bluetooth", "Pairing completed"  
      }  
    }  
    val pairResult = request.enqueue()  
  } catch (e: ValidicBluetoothException) {  
    Log.w("Bluetooth", e.getMessage())  
  }  
}

Reading

viewLifecycleScope.launch {  
  val request = ScanRequest(bluetoothPeripheral)  
  request.onStart = object : OnStartHandler {  
    override fun onStart() {  
      Log.i("Bluetooth", "Scanning starting")  
    }  
  }  
  request.onCompleted = object : OnCompleteHandler {  
    override fun onComplete{  
      Log.i("Bluetooth", "Reading completed")  
    }  
  }  
  try {  
    val scanResult: BluetoothScanResult = request.enqueue()  
    val readRequest: ReadRequest =  
      ReadRequest(scanResult.bluetoothPeripheral, scanResult.bluetoothDevice)  
    readRequest.onStart = object : OnStartHandler {  
      override fun onStart() {  
        Log.i("Bluetooth", "Reading starting")  
      }  
    }  
    readRequest.onCompleted = object : OnCompleteHandler {  
      override fun onComplete{  
        Log.i("Bluetooth", "Reading completed"  
      }  
    }  
    val readResult: ReadResult = request.enqueue()  
  } catch (e: ValidicBluetoothException) {  
    Log.w("Bluetooth", e.getMessage())  
  }  
}

Passive Read

After a PairRequest or a ReadRequest completes successfully, it is possible to set up reading in the background for a specific device.

viewLifecycleScope.launch {  
  val request = ScanRequest(bluetoothPeripheral)  
  request.onStart = object : OnStartHandler {  
    override fun onStart() {  
      Log.i("Bluetooth", "Scanning starting")  
    }  
  }  
  request.onCompleted = object : OnCompleteHandler {  
    override fun onComplete{  
      Log.i("Bluetooth", "Reading completed")  
    }  
  }  
  try {  
    val scanResult: BluetoothScanResult = request.enqueue()  
    val readRequest: ReadRequest =  
      ReadRequest(scanResult.bluetoothPeripheral, scanResult.bluetoothDevice)  
    readRequest.onStart = object : OnStartHandler {  
      override fun onStart() {  
        Log.i("Bluetooth", "Reading starting")  
      }  
    }  
    readRequest.onCompleted = object : OnCompleteHandler {  
      override fun onComplete{  
        Log.i("Bluetooth", "Reading completed"  
      }  
    }  
    val readResult: ReadResult = request.enqueue()  
    Log.i("Bluetooth", "Reading success")
      
val monitorRequest: MonitorDeviceRequest =
  MonitorDeviceRequest(readResult.bluetoothPeripheral, true);
val monitorDeviceResult = monitorRequest.enqueue()
Log.i("Bluetooth", "Enabled monitoring device in the background")
val disableRequest =
  MonitorDeviceRequest = MonitorDeviceRequest(readResult.bluetoothPeripheral, false);
Log.i("Bluetooth", "Disabled monitoring device in the background")
        } catch (e: ValidicBluetoothException) {  
    Log.w("Bluetooth", e.getMessage())  
  }  
}

To capture which devices are currently being monitored in the background, query the PassiveBluetoothManager.monitoredDevices property.

val devices: Set<String> = PassiveBluetoothManager.getInstance(context).getMonitoredDevices()

Listening for events can be accomplished setting a BluetoothListener on the PassiveBluetoothManager as described in the above section.

Java Interop

BluetoothRequests can be consumed in java clients by converting the request to a JDK CompletableFuture, ListenableFuture or Single depending on which async library is added to the client dependencies.

// app/build.gradle

dependencies {
  implementation "org.jetbrains.kotlinx:kotlinx-coroutines-rx2:1.8.0"
  implementation "org.jetbrains.kotlinx:kotlinx-coroutines-guava:1.8.0"
  implementation "org.jetbrains.kotlinx:kotlinx-coroutines-jdk8:1.8.0"
}

ListenableFuture

ExecutorService service = Executors.newSingleThreadExecutor();
try {
    ListenableFuture<BluetoothScanResult> resultFuture = ValidicBluetoothKt.asListenableFuture(new ScanRequest(BluetoothPeripheral.getPeripheralForID(1)));
    resultFuture.addListener(()->{
      try {
        BluetoothScanResult result = resultFuture.get();
        Log.i("Bluetooth", result.getDevice().getAddress());
      } catch (ExecutionException e) {
        e.printStackTrace();
      }
    }, service);
} catch (ValidicBluetoothException bluetoothException) {
    bluetoothException.printStackTrace();
}

CompletableFuture

ExecutorService service = Executors.newSingleThreadExecutor();
try {
    CompletableFuture<BluetoothScanResult> resultFuture = ValidicBluetoothKt.asCompletableFuture(new ScanRequest(BluetoothPeripheral.getPeripheralForID(1)));
    } catch (InterruptedException e) {
                e.printStackTrace();
            resultFuture.handleAsync((result, throwable)->{
        if(throwable!=null){ Log.e(throwable) }
        else {Log.i("Bluetooth", result.getDevice().getAddress()); }
    }, service);

} catch (ValidicBluetoothException bluetoothException) {
    bluetoothException.printStackTrace();
}

RxJava Single

Disposable disposable = null;
Single<BluetoothScanResult> resultSingle = ValidicBluetoothKt.asRxSingle(new ScanRequest(BluetoothPeripheral.getPeripheralForID(1)));
disposable = resultSingle.subscribe(result, throwable)->{
    if(throwable!=null){ Log.e(throwable) }
    else {Log.i("Bluetooth", result.getDevice().getAddress()); }
});

Special Considerations

Most supported Bluetooth devices function similarly and you can follow the instructions on this page to work with them using the Kotlin API.

However, a few of our supported devices have additional requirements and/or considerations that you need to be aware of. Those are covered in Bluetooth Behavior.