RxAndroidBle - How to disconnect all connected devices? - android

I am using awesome rxandroidble library for BLE control.
I keep connection between activities.
Before I start scanning, I want to disconnect all connected devices first.
Sometimes It is not working if there are many connections.
This is the solution I am using:
public void doScan() {
if (isScanning()) return;
// disconnect all connected devices first:
while(BleController.getDefault().getDisconnectTriggerSubject().hasObservers()){
BleController.getDefault().getDisconnectTriggerSubject().onNext(null);
}
scanSubscription = rxBleClient.scanBleDevices(
new ScanSettings.Builder()
.setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY)
.setCallbackType(ScanSettings.CALLBACK_TYPE_ALL_MATCHES)
.build(),
new ScanFilter.Builder()
// add custom filters if needed
.build()
)
.filter(rxBleScanResult -> !TextUtils.isEmpty(rxBleScanResult.getBleDevice().getName()))
.observeOn(AndroidSchedulers.mainThread())
.doOnUnsubscribe(this::clearSubscription)
.subscribe(resultsAdapter::addScanResult, this::onScanFailure);
updateButtonUIState();
}
BleController is initialized with the main application's context and keeps the connectionObservable, disconnectTriggerSubject, rxBleClient.
What can be the better solution?
Any help would be appreciated!

From your post I can see that you are mixing the BLE scanning/connection logic with the UI/Activity logic. This may be a problem to manage connections correctly.
What you could do is to put all the BLE logic to your BleController which already has a good name but it seems that in your situation is rather a BleObjectsContainer.
For instance you could only expose from the BleController only observables that are fulfilling your specific use-cases in a way that the Activities do not need to handle. i.e. Your BleController could handle scanning:
private final BehaviorRelay<Boolean> isScanningPublishRelay = BehaviorRelay.create(false); // a relay (that cannot emit an error) that emits when a scan is ongoing
private Observable<ScanResult> scanDevicesWithNonNullNameObs = rxBleClient.scanBleDevices(new ScanSettings.Builder().build())
.filter(scanResult -> !TextUtils.isEmpty(scanResult.getBleDevice().getName()))
.doOnSubscribe(() -> isScanningPublishRelay.call(true)) // when scan is subscribed/started emit true
.doOnUnsubscribe(() -> isScanningPublishRelay.call(false)) // when scan is stopped emit false
.subscribeOn(AndroidSchedulers.mainThread()) // the above emissions will happen on the same thread. should be serialized
.unsubscribeOn(AndroidSchedulers.mainThread()) // the above emissions will happen on the same thread. should be serialized
.share(); // share this observable so no matter how many places in the code will subscribe the scan is started once and `isScanningPublishRelay` is called once
public Observable<ScanResult> scanDevicesWithNonNullName() { // getter for the scan observable
return scanDevicesWithNonNullNameObs;
}
And besides of scanning it would also handle your specific use-cases for each Activity that needs it:
class ScanInProgress extends Throwable {
// ...
}
public Observable<YourActivityUseCaseModel> doYourSpecificStuff(Observable<RxBleConnection> connectionObservable) {
return Observable.merge(
connectionObservable,
isScanningPublishRelay
.takeFirst(aBoolean -> aBoolean)
.flatMap(ignored -> Observable.error(new ScanInProgress())) // this will only emit an error when a scan is ongoing
)
.flatMap(...); // do the rest of your stuff
}
This way in your activities you would only need to subscribe to whatever model they need and handle the BLE in a single place that is dedicated for it (BleController).
In the above example you need to provide the Observable<RxBleConnection> but it can be achieved in many different ways and could managed in BleController as well so it would not be exposed in the interface.

Related

Does offer make the coroutines close or cancelled?

I was looking at the flow documentation on the Android Developer site and I have a question.
https://developer.android.com/kotlin/flow#callback
If you look at the above link, you will see code like this.
class FirestoreUserEventsDataSource(
private val firestore: FirebaseFirestore
) {
// Method to get user events from the Firestore database
fun getUserEvents(): Flow<UserEvents> = callbackFlow {
// Reference to use in Firestore
var eventsCollection: CollectionReference? = null
try {
eventsCollection = FirebaseFirestore.getInstance()
.collection("collection")
.document("app")
} catch (e: Throwable) {
// If Firebase cannot be initialized, close the stream of data
// flow consumers will stop collecting and the coroutine will resume
close(e)
}
// Registers callback to firestore, which will be called on new events
val subscription = eventsCollection?.addSnapshotListener { snapshot, _ ->
if (snapshot == null) { return#addSnapshotListener }
// Sends events to the flow! Consumers will get the new events
try {
offer(snapshot.getEvents())
} catch (e: Throwable) {
// Event couldn't be sent to the flow
}
}
// The callback inside awaitClose will be executed when the flow is
// either closed or cancelled.
// In this case, remove the callback from Firestore
awaitClose { subscription?.remove() }
}
}
In the code above, awaitClose is explained to be executed when the coroutine is closed or cancelled.
But, there is no close() in the code except for the try-catch statement that initializes the eventsCollection.
Additionally, says offer does not add the element to the channel and **returns false** immediately at the bottom of the Android Developer page.
My question is, in the code above, when offer(snapshot.getEvents()) is executed, does the coroutine cancel with return false, so awaitClose is executed?
Expectation:
As the documentation says:
When you try to add a new element to a full channel, send suspends the
producer until there's space for the new element, whereas offer does
not add the element to the channel and returns false immediately.
Ergo:
It Immediately adds the specified element to this channel, if this doesn’t violate its capacity restrictions, and returns the successful result. Otherwise, returns failed or closed result. This is synchronous variant of send, which backs off in situations when send suspends or throws.
So when trySend call returns a non-successful result, it guarantees that the element was not delivered to the consumer, and it does not call onUndeliveredElement that was installed for this channel. See “Undelivered elements” section in Channel documentation for details on handling undelivered elements.
Conclusion:
A typical usage for onDeliveredElement is to close a resource that is being transferred via the channel. The following code pattern guarantees that opened resources are closed even if producer, consumer, and/or channel are cancelled. Resources are never lost. So no it doesn't return false.

How to enable/disable to notification/indication in RxAndroidBLE

I am creating a RxJava2 chain where in I want to enable and disable notification. the flow I am setting is as follows.
establish a connection.
set the notification to READ_STATUS UUID.
if the returned byte is zero then perform a write byte 01 to WRITE_STATUS UUID and after WRITE_STATUS, enable the notification of READ_STATUS UUID to verify it has byte value 1.
else if the returned byte is 1 then just enable other indicators (UUID1, UUID2,UUD3) and read the value.
I have a problem at step 2 and 3 where I am reading the value of READ_STATUS UUID by enabling the notification. in order to re-read the value, I probably need to disable the notification and then again enable it. And to disable to the notification I have to dispose that particular setupNotification .
Code is as follows
connectDisposable=
device.establishConnection(false)
.flatMap(rxBleConnection -> {
rxBleConnection.discoverServices();
mRxBleConnection = rxBleConnection;
return Observable.just(rxBleConnection);
})
.flatMap(rxBleConnection ->mRxBleConnection.setupNotification(READ_STATUS,NotificationSetupMode.QUICK_SETUP).flatMap(it->it))
.takeUntil(bytes -> {
if(getByteValue(bytes)==0)
return false;// dispose above to disable the notification
else
return true; // no need to disable the notification and continue writing
})
.flatMap(bytes -> {
return Observable.zip(
mRxBleConnection.writeCharacteristic(WRITE_STATUS, new byte[]{1}).toObservable(),
// setupNotification again to check whether read status has 1 or not
mRxBleConnection.setupNotification(READ_STATUS, NotificationSetupMode.QUICK_SETUP).flatMap(it->it),
Pair::new
);
})
.flatMap(bytes ->{
byte [] val= bytes.first;
if(getByteValue(val) == 1){
return Observable.zip(
mRxBleConnection.setupIndication(HISTORY, NotificationSetupMode.QUICK_SETUP).doOnNext(observable -> Log.e(TAG,"Here 1 ")).flatMap(it -> it),
mRxBleConnection.setupIndication(PARAMCHECK, NotificationSetupMode.QUICK_SETUP).doOnNext(observable -> Log.e(TAG,"Here 2 ")).flatMap(it -> it),
mRxBleConnection.setupIndication(FAULTINFO, NotificationSetupMode.QUICK_SETUP).doOnNext(observable -> Log.e(TAG,"Here 3 ")).flatMap(it -> it),
Data::Readings);
}
return Observable.empty();
}).subscribe(data -> {
});
The problem with this code is my takeUntil is firing at the last it does not dispose the previous setupNotificaion operation so that I can re read it later.
I tried solution mentioned over this thread but unfortunately I am not sharing the RxBleConnection
The problem with this code is my takeUntil is firing at the last it does not dispose the previous setupNotificaion operation so that I can re read it later.
The problem is that your condition is inverted. From .takeUntil() Javadoc:
* #return an Observable that first emits items emitted by the source Observable, checks the specified
* condition after each item, and then completes when the condition is satisfied.
You have used:
.takeUntil(bytes -> {
if(getByteValue(bytes)==0)
return false;// dispose above to disable the notification
else
return true; // no need to disable the notification and continue writing
})
where it should be satisfied (return true) when the upstream should get disposed:
.takeUntil(bytes -> {
if(getByteValue(bytes)==0)
return true;// dispose above to disable the notification
else
return false; // no need to disable the notification and continue writing
})
To unsubscribe or to dispose setupNotification or setupIndication one can use the following code. I am sure there could be different ways but so far I could find this
private Observable<Pair<byte[],byte[]>> getValueFromIndication(RxBleConnection rxBleConnection){
final PublishSubject<Boolean> unsubscribeOperation= PublishSubject.create();
return Observable.zip(
rxBleConnection.setupIndication(TSDictionary.FAULT_RETRY_COUNT_SEQUENCE,NotificationSetupMode.QUICK_SETUP).flatMap(it->it).takeUntil(unsubscribeOperation),
rxBleConnection.setupIndication(TSDictionary.FAULT_RETRY_INFORMATION,NotificationSetupMode.QUICK_SETUP).flatMap(it->it).takeUntil(unsubscribeOperation),
(bytes, bytes2) -> {
unsubscribeOperation.onNext(true);
return Pair.create(bytes,bytes2);
}
);
}
In above code, I am zipping two indication operations and once I get the value from it I am unsubscribing from the change chain using PublishSubject and takeUntil.

Suspending a Kotlin coroutine until a flow has a specific value

I am currently playing around with Kotlin coroutines and flows. In my scenario, a MutableStateFlow represents a connection state (CONNECTING, CONNECTED, CLOSING, CLOSED). It is also possible to login, logout and login again.
For further use of the connection, I have to check the state and wait until it is CONNECTED. If it is already CONNECTED, I can continue. If not, I have to wait until the state reaches CONNECTED. The connect() call does return immediately, the result is propagated via a callback that updates the MutableStateFlow. My current idea is to do the following:
connect()
if (connectionState.value != State.CONNECTED) { // connectionState = MutableStateFlow(State.CLOSED)
suspendCoroutine<Boolean> { continuation ->
scope.launch { // scope = MainScope()
connectionState.collect {
if (it == State.CONNECTED) {
continuation.resume(true)
}
}
}
}
}
// continue
As I am fairly new to the topic, I don't know if this is good practice and I was also not able to find a more suitable concept in the Kotlin documenation. Is there some better way of doing it?
A while back I had the same question:
It is preferred to use first() to suspend till the predicate is matched.
if (connectionState.value != State.CONNECTED) {
connectionState.first { it == State.CONNECTED }
}

Determine Characteristic Notfication emission count by first emission

I am currently implementing a protocol for a Bluetooth device and i am using the RxAndroidBle Library (version 1.4.3).
I have to request data from the device by writing to characteristic and then listening to the response via a characteristic notification.
To combine the 2 operations (writing and listening) I am using the code from: https://stackoverflow.com/a/41140523/734385
connectionObservable
.flatMap( // when the connection is available...
rxBleConnection -> rxBleConnection.setupNotification(AP_SCAN_DATA), // ... setup the notification...
(rxBleConnection, apScanDataNotificationObservable) -> Observable.combineLatest( // ... when the notification is setup...
rxBleConnection.writeCharacteristic(AP_SCAN_DATA, writeValue), // ... write the characteristic...
apScanDataNotificationObservable.first(), // ... and observe for the first notification on the AP_SCAN_DATA
(writtenBytes, responseBytes) -> responseBytes // ... when both will appear return just the response bytes...
)
)
.flatMap(observable -> observable)
This approach works for me, the only problem is that the code gives me only the first 20 bytes (due to the apScanDataNotificationObservable.first()).
Unfortunately, I don't know the size of the package I am receiving. I can only extract the information from the header of the first 20 bytes. It seems like the RxJava buffer function all require to know the size beforehand.
Is there a way to make this work cleanly with the code above as part of the Rx chain?
In other words, can I control the number of emission based on the very first emission of an Rx chain?
Or do I have a completely wrong approach?
It is possible to achieve what you want.
The easiest way would be to exchange the Observable.combineLatest(...) to:
Observable.merge(
rxBleConnection.writeCharacteristic(AP_SCAN_DATA, writeValue).ignoreElements(), // send the request but ignore the returned value
apScanDataNotificationObservable.takeUntil(newResponseEndWatcher()) // take the response notifications until the response end watcher says so
);
Where newResponseEndWatcher() would need to contain the logic for determining if the received values are all that is expected. It could look like this:
private Func1<byte[], Boolean> newResponseEndWatcher() {
return new Func1<byte[], Boolean>() {
private static final int NOT_INITIALIZED = -1;
private int totalLength = NOT_INITIALIZED;
private int receivedLength = NOT_INITIALIZED;
#Override
public Boolean call(byte[] bytes) {
if (isNotInitialized(totalLength)) { // if it is the first received value
// parse totalLength from the header
}
// update receivedLength
return receivedLength >= totalLength;
}
private boolean isNotInitialized(int value) {
return value == NOT_INITIALIZED;
}
};
}
Just have in mind that Func1 which is the result newResponseEndWatcher() is stateful. If one would store into a variable the observable that is result of apScanDataNotificationObservable.takeUntil(newResponseEndWatcher()) the next subscriptions could end prematurely.
To mitigate this problem one may use Observable.using() function that would call newResponseEndWatcher() each time it is subscribed and then create a new apScanDataNotificationObservable.takeUntil(newResponseEndWatcher):
Observable.using(
() -> newResponseEndWatcher(), // create a new response end watcher on each subscription
responseEndWatcher -> apScanDataNotificationObservable.takeUntil(responseEndWatcher), // create the response observable that will complete properly
responseEndWatcher -> { /* ignored, responseEndWatcher will get GCed eventually */ }
);

Android: Polling a server with Retrofit

I'm building a 2 Player game on Android. The game works turnwise, so player 1 waits until player 2 made his input and vice versa. I have a webserver where I run an API with the Slim Framework. On the clients I use Retrofit. So on the clients I would like to poll my webserver (I know it's not the best approach) every X seconds to check whether there was an input from player 2 or not, if yes change UI (the gameboard).
Dealing with Retrofit I came across RxJava. My problem is to figure out whether I need to use RxJava or not? If yes, are there any really simple examples for polling with retrofit? (Since I send only a couple of key/value pairs) And if not how to do it with retrofit instead?
I found this thread here but it didn't help me too because I still don't know if I need Retrofit + RxJava at all, are there maybe easier ways?
Let's say the interface you defined for Retrofit contains a method like this:
public Observable<GameState> loadGameState(#Query("id") String gameId);
Retrofit methods can be defined in one of three ways:
1.) a simple synchronous one:
public GameState loadGameState(#Query("id") String gameId);
2.) one that take a Callback for asynchronous handling:
public void loadGameState(#Query("id") String gameId, Callback<GameState> callback);
3.) and the one that returns an rxjava Observable, see above. I think if you are going to use Retrofit in conjunction with rxjava it makes the most sense to use this version.
That way you could just use the Observable for a single request directly like this:
mApiService.loadGameState(mGameId)
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Subscriber<GameState>() {
#Override
public void onNext(GameState gameState) {
// use the current game state here
}
// onError and onCompleted are also here
});
If you want to repeatedly poll the server using you can provide the "pulse" using versions of timer() or interval():
Observable.timer(0, 2000, TimeUnit.MILLISECONDS)
.flatMap(mApiService.loadGameState(mGameId))
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Subscriber<GameState>() {
#Override
public void onNext(GameState gameState) {
// use the current game state here
}
// onError and onCompleted are also here
}).
It is important to note that I am using flatMap here instead of map - that's because the return value of loadGameState(mGameId) is itself an Observable.
But the version you are using in your update should work too:
Observable.interval(2, TimeUnit.SECONDS, Schedulers.io())
.map(tick -> Api.ReceiveGameTurn())
.doOnError(err -> Log.e("Polling", "Error retrieving messages" + err))
.retry()
.observeOn(AndroidSchedulers.mainThread())
.subscribe(sub);
That is, if ReceiveGameTurn() is defined synchronously like my 1.) above, you would use map instead of flatMap.
In both cases the onNext of your Subscriber would be called every two seconds with the latest game state from the server. You can process them one after another of limit the emission to a single item by inserting take(1) before subscribe().
However, regarding the first version: A single network error would be first delivered to onError and then the Observable would stop emitting any more items, rendering your Subscriber useless and without input (remember, onError can only be called once). To work around this you could use any of the onError* methods of rxjava to "redirect" the failure to onNext.
For example:
Observable.timer(0, 2000, TimeUnit.MILLISECONDS)
.flatMap(new Func1<Long, Observable<GameState>>(){
#Override
public Observable<GameState> call(Long tick) {
return mApiService.loadGameState(mGameId)
.doOnError(err -> Log.e("Polling", "Error retrieving messages" + err))
.onErrorResumeNext(new Func1<Throwable, Observable<GameState>(){
#Override
public Observable<GameState> call(Throwable throwable) {
return Observable.emtpy());
}
});
}
})
.filter(/* check if it is a valid new game state */)
.take(1)
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Subscriber<GameState>() {
#Override
public void onNext(GameState gameState) {
// use the current game state here
}
// onError and onCompleted are also here
}).
This will every two seconds:
* use Retrofit to get the current game state from the server
* filter out invalid ones
* take the first valid one
* and the unsubscribe
In case of an error:
* it will print an error message in doOnNext
* and otherwise ignore the error: onErrorResumeNext will "consume" the onError-Event (i.e. your Subscriber's onError will not be called) and replaces it with nothing (Observable.empty()).
And, regarding the second version: In case of a network error retry would resubscribe to the interval immediately - and since interval emits the first Integer immediately upon subscription the next request would be sent immediately, too - and not after 3 seconds as you probably want...
Final note: Also, if your game state is quite large, you could also first just poll the server to ask whether a new state is available and only in case of a positive answer reload the new game state.
If you need more elaborate examples, please ask.
UPDATE: I've rewritten parts of this post and added more information in between.
UPDATE 2: I've added a full example of error handling with onErrorResumeNext.
Thank you, I finally made it in a similar way based the post I referred to in my question. Here's my code for now:
Subscriber sub = new Subscriber<Long>() {
#Override
public void onNext(Long _EmittedNumber)
{
GameTurn Turn = Api.ReceiveGameTurn(mGameInfo.GetGameID(), mGameInfo.GetPlayerOneID());
Log.d("Polling", "onNext: GameID - " + Turn.GetGameID());
}
#Override
public void onCompleted() {
Log.d("Polling", "Completed!");
}
#Override
public void onError(Throwable e) {
Log.d("Polling", "Error: " + e);
}
};
Observable.interval(3, TimeUnit.SECONDS, Schedulers.io())
// .map(tick -> Api.ReceiveGameTurn())
// .doOnError(err -> Log.e("Polling", "Error retrieving messages" + err))
.retry()
.subscribe(sub);
The problem now is that I need to terminate emitting when I get a positive answer (a GameTurn). I read about the takeUntil method where I would need to pass another Observable which would emit something once which would trigger the termination of my polling. But I'm not sure how to implement this.
According to your solution, your API method returns an Observable like it is shown on the Retrofit website. Maybe this is the solution? So how would it work?
UPDATE:
I considered #david.miholas advices and tried his suggestion with retry and filter. Below you can find the code for the game initialization. The polling should work identically: Player1 starts a new game -> polls for opponent, Player2 joins the game -> server sends to Player1 opponent's ID -> polling terminated.
Subscriber sub = new Subscriber<String>() {
#Override
public void onNext(String _SearchOpponentResult) {}
#Override
public void onCompleted() {
Log.d("Polling", "Completed!");
}
#Override
public void onError(Throwable e) {
Log.d("Polling", "Error: " + e);
}
};
Observable.interval(3, TimeUnit.SECONDS, Schedulers.io())
.map(tick -> mApiService.SearchForOpponent(mGameInfo.GetGameID()))
.doOnError(err -> Log.e("Polling", "Error retrieving messages: " + err))
.retry()
.filter(new Func1<String, Boolean>()
{
#Override
public Boolean call(String _SearchOpponentResult)
{
Boolean OpponentExists;
if (_SearchOpponentResult != "0")
{
Log.e("Polling", "Filter " + _SearchOpponentResult);
OpponentExists = true;
}
else
{
OpponentExists = false;
}
return OpponentExists;
}
})
.take(1)
.subscribe(sub);
The emission is correct, however I get this log message on every emit:
E/Polling﹕ Error retrieving messages: java.lang.NullPointerException
Apperently doOnError is triggered on every emit. Normally I would get some Retrofit debug logs on every emit which means that mApiService.SearchForOpponent won't get called. What do I do wrong?

Categories

Resources