onScanFailure after a certain of time when scanning ble devices in android - rxandroidble

I am using rxbleandroid library for scanning Bluetooth devices
fun startScan() {
if(rxBleClient.isScanRuntimePermissionGranted) {
Log.d("TAG", "All permission granted")
}
else {
Log.d("TAG", "Not all permission granted\"")
}
val scanSettings = ScanSettings.Builder()
.setScanMode(SCAN_MODE_BALANCED)
.setCallbackType(ScanSettings.CALLBACK_TYPE_ALL_MATCHES)
.build()
val scanFilter = ScanFilter.Builder()
.build()
rxBleClient.scanBleDevices(scanSettings, scanFilter)
.observeOn(AndroidSchedulers.mainThread())
.doFinally { dispose() }
.subscribe({scanResult ->
Log.d("TAG", scanResult?.bleDevice?.name!!)
},
{
onScanFailure(it) })
.let {
scanDisposable = it}//
}
private fun dispose() {
//scanDisposable = null
}
private fun onScanFailure(throwable: Throwable) {
Log.d("TAG", "onScanFailure ${throwable. Cause}")
viewModelScope.launch{
delay(6000)
scanDisposable?.dispose()
startScan()
}
}
After a short while a get a onScanFailure event. The output on the screen is
onScanFailure null
The only way to fix it is to wait little bit and restart the scanning.
I am using the latest version of android on a pixel6. The version number of rxAndroidLibraryble is 1.17.0
Am i using the library wrong?

In the callback function you are using this line:
Log.d("TAG", scanResult?.bleDevice?.name!!)
It will throw an NPE once a device with no advertised name will get scanned and that NPE will have no cause.
You could use this:
Log.d("TAG", scanResult?.bleDevice?.name ?: "no name")

Related

RxAndroidBle can't disconnect

I encountered a situation
When I connect 2 devices at once
When the connection is successful, the notification subcribe is listened to
When one is off and the other is in use
The program keeps trying to connect the two devices every 5 seconds
but because the connection is not disconnected
so it has to wait until the one that is switched off GATT ERROR
The one that is in use will not successfully connect to receive data
This is a bit of a long wait.
I want to disconnect manually after receiving the data
So that the next time I connect, I don't have to wait for GATT ERROR.
I don't know if this will improve the long waiting time of the one in use.
I have tried nRF connect
When I disconnected it manually
I can quickly connect with another
class BleDevice : BaseDevice() {
lateinit var context: Context
private var macAddress : String = "00:00:00:00:00:00"
//Search Device behavior
//If true, always search no interrupt
private var establishConnection = false
private var setupNotificationMap = arrayListOf<Disposable>()
private var connectionDisposable: Disposable? = null
private var sendDisposable: Disposable? = null
private var connection: RxBleConnection? = null
override fun work() {
if((strategy as BaseBleVariables).enableSetMTU()) {
connection?.requestMtu((strategy as BaseBleVariables).getMTUSize())
?.subscribe(
{ it ->
Log.d(logTag, "Set MTU = $it")
},
{ exception ->
exception.printStackTrace()
}
)?.let { disposable ->
setupNotificationMap.add(disposable)
}
}
(strategy as BaseBleVariables).getNotificationMap().forEach { uuid ->
Log.d(logTag, "getNotification")
connection?.setupNotification(UUID.fromString(uuid))
?.subscribe(
{
it.subscribe(
{ receive ->
strategy.onRespond(
receive,
Bundle().apply { putString("Characteristic", uuid) }
)
},
{ exception ->
Log.d(logTag, "Receive Response Fail")
exception.printStackTrace()
disconnect(exception.message)
}
).let { disposable ->
setupNotificationMap.add(disposable)
}
},
{ exception ->
Log.d(logTag, "Setup Notification Fail")
exception.printStackTrace()
disconnect(exception.message)
}
)?.let { disposable ->
setupNotificationMap.add(disposable)
}
}
strategy.onSend(null)
}
override fun stopWork() {
setupNotificationMap.forEach {
if(!it.isDisposed) {
it.dispose()
}
}
sendDisposable?.let {
if(!it.isDisposed) {
Log.d(logTag, "SendDisposable dispose")
it.dispose()
}
}
setupNotificationMap.clear()
}
#ExperimentalStdlibApi
override fun connect() {
if(strategy !is BaseBleVariables) {
Log.d(logTag, "Strategy not impl ble variables")
returnConnectionStatus(false, "Strategy incorrectly implement")
return
}
RxJavaPlugins.setErrorHandler { throwable: Throwable ->
throwable.printStackTrace()
}
connectionDisposable = RxBleClient.create(context)
.getBleDevice(macAddress)
.establishConnection(establishConnection)
.subscribe(
{
connection = it
returnConnectionStatus(true)
},
{
Log.d(logTag, "Client Disconnected")
it.printStackTrace()
disconnect(it.message)
}
)
}
override fun disconnect(message: String?) {
runCatching {
stopWork()
connectionDisposable?.let {
if(!it.isDisposed) {
Log.d(logTag, "ConnectionDisposable dispose")
it.dispose()
}
}
connection = null
returnConnectionStatus(false, message)
}.onFailure {
returnConnectionStatus(false, message)
}
}
override fun receive(): Result<ByteArray> {
return Result.success(byteArrayOf())
}
override fun send(command: Any, extras: Bundle?): Result<Any> {
if(connection == null) {
return Result.failure(Exception("Connection is null"))
}
else {
return runCatching {
when(command) {
is ByteArray -> {
Log.d(logTag, "Delay : ${extras?.getLong("SendDelay", 0L)}")
Log.d(logTag, "Send : ${command.contentToString()}")
extras?.getString("Characteristic")?.let { characteristic ->
sendDisposable?.let {
if(!it.isDisposed) {
it.dispose()
}
}
sendDisposable = connection?.writeCharacteristic(
(UUID.fromString(characteristic)),
command
)?.delaySubscription(extras.getLong("SendDelay", 0L), TimeUnit.MILLISECONDS)
?.subscribe(
{
Log.d(logTag, "Write Characteristic($characteristic) = ${String(it)}")
},
{
Log.d(logTag, "Send Fail")
it.printStackTrace()
}
)
}
"Nothing"
}
else -> {
Log.d(logTag, "This command type not support now")
}
}
}
}
}
class Builder<T: BaseStrategy>(strategyClass: Class<T>, _context: Context): BaseDevice.Builder<BleDevice, T>(BleDevice::class.java, strategyClass){
private var logTag = "BleBuilder"
init {
device.context = _context
}
fun setMacAddress(address: String) : Builder<T> {
device.macAddress = address
Log.d(logTag, "Set MacAddress = ".plus(address))
return this
}
/**
* Search Device behavior
* If true, always search no interrupt
*/
fun setEstablishConnection(establish: Boolean) : Builder<T> {
device.establishConnection = establish
Log.d(logTag, "Set EstablishConnection = ".plus(establish))
return this
}
}
}
To use it, remember the mac address of the two Bluetooth devices
Then use the for loop to connect one device in 5 seconds
When the device is connected, dispose of it and connect to the other one which is not connected.
I have also tried for looping two units at a time in five seconds
When connecting
When both devices are on, the data can be transferred back quickly
When one of them is switched off
When one is switched off, the switched on device must wait for the switched off device to automatically Gatt ERROR
The one that is switched on will only connect and send data
But when using the nRF connect app
When I manually disconnect the one that is off
the other one can connect and send back data very quickly
RxAndroidBle Author's Response
RxAndroidBle was designed to not have any state management (i.e. disconnection function) — when the user is no longer interested in keeping the connection they just dispose the flow.
You should be able to dispose your connection when you receive the data

How to run a Firebase Transaction using Kotlin Coroutines?

I'm trying to run a Firebase Transaction under a suspended function in Kotlin and i see no documentation about it.
I'm using
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-play-services:1.5.2'
for coroutines with firebase (eg: setValue(*).await() ) but there seems to be no await function for runTransaction(*)
override suspend fun modifyProductStock(
product: ProductModel,
valueToModify: Long,
replace: Boolean
) {
CoroutineScope(Dispatchers.Main).launch {
val restaurantId = authRepository.restaurantId.value ?: throw Exception("No restaurant!")
val productId = product.id ?: throw Exception("No Product ID!")
val reference = FirebaseDatabase.getInstance().getReference("database/$restaurantId").child("products")
if (replace) {
reference.child(productId).child("stock").setValue(valueToModify).await()
} else {
reference.child(productId).child("stock")
.runTransaction(object : Transaction.Handler {
override fun doTransaction(p0: MutableData): Transaction.Result {
//any operation
return Transaction.success(p0)
}
override fun onComplete(p0: DatabaseError?, p1: Boolean, p2: DataSnapshot?) {
}
})
}
}
}
You could wrap it in suspendCoroutine:
val result: DataSnapshot? = suspendCoroutine { c ->
reference.child(productId).child("stock")
.runTransaction(object : Transaction.Handler {
override fun doTransaction(p0: MutableData): Transaction.Result {
//any operation
return Transaction.success(p0)
}
override fun onComplete(error: DatabaseError?, p1: Boolean, snapshot: DataSnapshot?) {
c.resume(snapshot)
}
})
}
suspendCoroutine
Obtains the current continuation instance inside suspend functions and suspends the currently running coroutine.
In this function both Continuation.resume and Continuation.resumeWithException can be used either synchronously in the same stack-frame where the suspension function is run or asynchronously later in the same thread or from a different thread of execution.
Given that the Kotlin example in the Firebase documentation on transactions uses the same callback style that you have, it seems indeed that there is no specific support for co-routines there.
It might be worth posting an issue on the Android SDK repo to get it added, or hear why it wasn't added.

Checking if username already exists gives error kotlin

I translated the code below from java, but I am getting an error in some places, why is it giving an error, is there a point I missed?
val mQuery: Query = firestore.collection("users")
.whereEqualTo("nickname", mUserName)
mQuery.addSnapshotListener(object : EventListener<QuerySnapshot> {
fun onEvent(
documentSnapshots: QuerySnapshot,
e: FirebaseFirestoreException?
) {
for (ds in documentSnapshots) {
if (ds != null) {
val userName: String = document.getString("username")
Log.d(
TAG,
"checkingIfusernameExist: FOUND A MATCH: $userName"
)
Toast.makeText(
this#SignUpActivity,
"That username already exists.",
Toast.LENGTH_SHORT
).show()
}
}
}
})
I've been doing the things described here for 2-3 days, but it keeps throwing an error.Event listeners and docs throw errors.
I translated the code below from java
That's not the correct way of "translating" that code from Java to Kotlin, since that answer provides a solution for getting data only once. So to be able to do that in Kolin programming language please use the following lines of code:
val rootRef = FirebaseFirestore.getInstance()
val allUsersRef = rootRef.collection("all_users")
val userNameQuery = allUsersRef.whereEqualTo("username", "userNameToCompare")
userNameQuery.get().addOnCompleteListener { task ->
if (task.isSuccessful) {
for (document in task.result) {
if (document.exists()) {
Log.d("TAG", "username already exists")
val userName = document.getString("username")
//Do what you need to do with the userName
} else {
Log.d("TAG", "username does not exists")
}
}
} else {
Log.d("TAG", "Error getting documents: ", task.exception)
}
}

JavaFX, Is there any callback for Platform.runlater()?

My application adds multiple Runnable on FX thread, by calling Platform.runlater method, then, I want to do some calculations only when there is no additional Runnable is in the FX Platform queue.
But I don't know the right way, is there any event or callback mechanism to get the right time?
Currently I force the application thread to sleep randomly MILLISECONDS.
This is a bad idea from the start since you do not know what other code uses Platform.runLater. Also you should not rely on such implementation details; for all you know the queue could never be empty.
However you could post those Runnables using a custom class that keeps track of the number of Runnables and notifies you when all are done:
public class UpdateHandler {
private final AtomicInteger count;
private final Runnable completionHandler;
public UpdateHandler(Runnable completionHandler, Runnable... initialTasks) {
if (completionHandler == null || Stream.of(initialTasks).anyMatch(Objects::isNull)) {
throw new IllegalArgumentException();
}
count = new AtomicInteger(initialTasks.length);
this.completionHandler = completionHandler;
for (Runnable r : initialTasks) {
startTask(r);
}
}
private void startTask(Runnable runnable) {
Platform.runLater(() -> {
runnable.run();
if (count.decrementAndGet() == 0) {
completionHandler.run();
}
});
}
public void Runnable runLater(Runnable runnable) {
if (runnable == null) {
throw new IllegalArgumentException();
}
count.incrementAndGet();
startTask(runnable);
}
}

Realm.firstFirstAsync().asObservable() isn't consistently working with RxJava.switchIfEmpty

I am trying to create function which reads a object from realm and emit an empty observable if the object isn't found. The code below works to some degree because I can stop it with the debugger and see it hit the Observable.empty():
fun readFromRealm(id: String): Observable<Player> {
return realm.where(Player::class.java)
.equalTo("id", id)
.findFirstAsync()
.asObservable<Player>()
.filter { it.isLoaded }
.flatMap {
if (it.isValid)
Observable.just(it)
else
Observable.empty()
}
}
But when I try to use a switchIfEmpty on the Observable the code never emits defaultPlayer when it is not found in realm.
return readFromRealm(playerId)
.take(1)
.map{ // do something with emitted observable }
.switchIfEmpty(Observable.just(defaultPlayer)) // use this if no player found
The strange thing is that if I update the original method to include a first() prior to the flatMap :
fun readFromRealm(id: String): Observable<Player> {
return realm.where(Player::class.java)
.equalTo("id", id)
.findFirstAsync()
.asObservable<Player>()
.filter { it.isLoaded }
.first() // add first
.flatMap {
if (it.isValid)
Observable.just(it)
else
Observable.empty()
}
}
Everything starts working as expected, but I believe this version will kill auto updating because it will only capture the first result emitted after the filter.
I'm still trying to grok Realm and Rx so I'm probably doing something dumb.
EDIT: I have created a sample project which highlights the issue I'm seeing - https://github.com/donaldlittlepie/realm-async-issue
For reasons I don't totally understand. If you move take(1) just above the
flatMap and below the filter it should work correctly:
realm.where(Dog.class)
.equalTo("id", 0L)
.findFirstAsync()
.asObservable()
.cast(Dog.class)
.filter(new Func1<RealmObject, Boolean>() {
#Override
public Boolean call(RealmObject realmObject) {
return realmObject.isLoaded();
}
})
.take(1) // <== here
.flatMap(new Func1<Dog, Observable<Dog>>() {
#Override
public Observable<Dog> call(Dog realmObject) {
if (realmObject.isValid()) {
return Observable.just(realmObject);
} else {
return Observable.empty();
}
}
})
.map(new Func1<Dog, Dog>() {
#Override
public Dog call(Dog dog) {
dog.setName("mapped " + dog.getName());
return dog;
}
})
.switchIfEmpty(Observable.just(createDefaultDog()))
.subscribe(new Action1<Dog>() {
#Override
public void call(Dog dog) {
textView.setText(dog.getName());
}
}, new Action1<Throwable>() {
#Override
public void call(Throwable throwable) {
textView.setText(throwable.toString());
}
});
My best guess is that before, flatMap was called repeatedly, returning Observable.empty() multiple times. Perhaps that effects the Observable chain in some unexpected way.

Resources