I'm currently trying to use rxandroidble in order to replace the native BLE API of Android of one of our app.
How to disable a notification? I'm able to enable it with the sample code, this one:
device.establishConnection(context, false)
.flatMap(rxBleConnection -> rxBleConnection.setupNotification(characteristicUuid))
.doOnNext(notificationObservable -> { // OK })
.flatMap(notificationObservable -> notificationObservable)
.subscribe(bytes -> { // OK });
But in my product I have a use case where I have to disable / enable the notification(s) on demand.
Plus, I tried to directly unsubscribe / reconnect instead of disable / enable the notification but the unsubscribe command is never executed apparently, my hypothesis is because I have a high throughput (my device notifies at 300 - 400Hz), is it plausible?
(I know that BLE is not the most appropriate technology for high throughput but it's for R&D purpose here :) )
Thanks for your help!
Enabling notifications happens whenever the Observable from RxBleConnection.setupNotification() will be subscribed. To disable the notification one must unsubscribe from the above subscription.
There are several ways in which it can be coded. One of them is:
final RxBleDevice rxBleDevice = // your RxBleDevice
final Observable<RxBleConnection> sharedConnectionObservable = rxBleDevice.establishConnection(this, false).share();
final Observable<Boolean> firstNotificationStateObservable = // an observable that will emit true when notification should be enabled and false when disabled
final UUID firstNotificationUuid = // first of the needed UUIDs to enable / disable
final Subscription subscription = firstNotificationStateObservable
.distinctUntilChanged() // to be sure that we won't get more than one enable commands
.filter(enabled -> enabled) // whenever it will emit true
.flatMap(enabled -> sharedConnectionObservable // we take the shared connection
.flatMap(rxBleConnection -> rxBleConnection.setupNotification(firstNotificationUuid)) // enable the notification
.flatMap(notificationObservable -> notificationObservable) // and take the bytes
.takeUntil(firstNotificationStateObservable.filter(enabled1 -> !enabled1)) // and we are subscribing to this Observable until we want to disable - note that only the observable from sharedConnectionObservable will be unsubscribed
)
.subscribe(
notificationBytes -> { /* handle the bytes */ },
throwable -> { /* handle exception */ }
);
Note that in the above example the connection will be closed whenever the last subscription to sharedConnectionObservable will end.
To enable / disable different characteristics you can copy and paste the above code with different Observable<Boolean> as enable / disable inputs and different UUID's.
Kotlin
In kotlin, Use dispose() to disable the notification.
#ENABLE NOTIFICATION
val result = device.establishConnection(false)
.flatMap(rxBleConnection -> rxBleConnection.setupNotification(characteristicUuid))
.doOnNext(notificationObservable -> {
// Notification has been set up
})
.flatMap(notificationObservable -> notificationObservable) // <-- Notification has been set up, now observe value changes.
.subscribe(
bytes -> {
// Given characteristic has been changes, here is the value.
},
throwable -> {
// Handle an error here.
}
);
#DISABLE NOTIFICATION
result.dispose()
Tried to unsubscribe but it didn't work, still not sure which one is best dispose() or unsubscribe()
Related
I am trying to read the value after firing the rxBleConnection.setupIndication(UUID) I have many characteristics UUIDs and I want to line up using RxJava in a way that I can get consolidated values just the way we get using Single.Zip or Observable.zip
For example, using RxAndroidBle we can read multiple characteristics, would it be possible to do the same for setupIndication. As setupIndication is returning Observable<Observable<byte[]>> it is not possible to zip i guess.
here is the library that I am using
What I expect
Disposable disposable = device.establishConnection(false)
.flatMap(rxBleConnection -> Observable.zip(
rxBleConnection.setupIndication(UUID1),
rxBleConnection.setupIndication(UUID2),
rxBleConnection.setupIndication(UUID3),
rxBleConnection.setupIndication(UUID4),
BLEReading::new
))
.subscribe(
model -> {
// Process your model.
Log.e(TAG , "FINAL DATA ");
},
throwable -> {
// Handle an error here.
}
);
Currently, I have to perform the setupIndication for all 5 charactericts.
connectDisposible = device.establishConnection(false)
.flatMap(rxBleConnection->rxBleConnection.setupIndication(UUID1))
.flatMap(notificationObservable -> notificationObservable)
.subscribe(
bytes -> {
Log.e(TAG,"Notification bytes"+Arrays.toString(BLEUtils.toHex(bytes)));
},
throwable -> {
Log.e(TAG,"Notification Error "+throwable.getMessage());
}
);
Edit
connectDisposible = device.establishConnection(false)
.flatMap(rxBleConnection -> Observable.zip(
rxBleConnection.setupIndication(UUID1).flatMap(it -> it),
rxBleConnection.setupIndication(UUID2).flatMap(it -> it),
rxBleConnection.setupIndication(UUID3).flatMap(it -> it),
rxBleConnection.setupIndication(UUID4).flatMap(it -> it),
rxBleConnection.setupIndication(UUID5).flatMap(it -> it),
BLEReading::new
))
.subscribe(
model -> {
//control never reaches here
Log.e(TAG , "FINAL DATA "+model);
},
throwable -> {
// Handle an error here.
Log.e(TAG , "error"+throwable.getMessage());
}
);
however, in logcat i can the indications are successfully set.
setCharacteristicNotification() - uuid: 705f68f7-83c9-6562-b2c5 enable: true
setCharacteristicNotification() - uuid: 314fae3a-d0cf-51c4-4a67 enable: true
setCharacteristicNotification() - uuid: 8599c5ba-f827-2d16-ce14 enable: true
setCharacteristicNotification() - uuid: 6fbba050-e87b-6ea8-6e5d enable: true
The simplest way to have all indications lined up is to flatMap the indication observables into individual indications. Just have in mind that each emission to the subscribe block will happen when all of the indication observables will emit.
Using NotificationSetupMode.QUICK_SETUP to not miss emissions that happen in response to setting Client Characteristic Configuration (CCC) Descriptor.
Disposable disposable = device.establishConnection(false)
.flatMap(rxBleConnection -> Observable.zip(
rxBleConnection.setupIndication(UUID1, NotificationSetupMode.QUICK_SETUP).flatMap(it -> it),
rxBleConnection.setupIndication(UUID2, NotificationSetupMode.QUICK_SETUP).flatMap(it -> it),
rxBleConnection.setupIndication(UUID3, NotificationSetupMode.QUICK_SETUP).flatMap(it -> it),
rxBleConnection.setupIndication(UUID4, NotificationSetupMode.QUICK_SETUP).flatMap(it -> it),
BLEReading::new
))
.subscribe(
model -> {
// Process your model.
Log.e(TAG , "FINAL DATA ");
},
throwable -> {
// Handle an error here.
}
);
I'm trying to subscribe to notification changes from a BLE device that I built, and print the value of the notification. I know the characteristic UUID that I want to read the notifications from:
scanSubscription = rxBleClient.scanBleDevices(
ScanSettings.Builder().build(),
ScanFilter.Builder().setDeviceName("MyDevice").build() // Filter for devices named MyDevice
)
.take(1) // stop the scan when a matching device will be scanned for the first time
.flatMap {
val device = it.bleDevice
device.establishConnection(false)
.flatMap < Any > {
rxBleConnection: RxBleConnection - > rxBleConnection.setupNotification(charUUID)
}
.doOnNext {
notificationObservable: Any ? - >
}
.flatMap < Any > {
notificationObservable: Any ? - > notificationObservable
} // <-- Notification has been set up, now observe value changes.
}
.subscribe({ /* written */ }, {
throwable - >
// Handle an error here.
// println("Scan Error: $throwable")
})
To prove that the device is working as intended, I observe the notification changes using the BLE Scanner app for Android.
When I wave my hand over the sensor, the value changes. In my case, it increments.
My question is how can I print that value when the notification value changes? When debugging, I can't get the Count to print. It doesn't even appear in my console.
Usually on Android one uses Log class to log data to logcat. You could use it like this:
scanSubscription = rxBleClient.scanBleDevices(
ScanSettings.Builder().build(),
ScanFilter.Builder().setDeviceName("MyDevice").build() // Filter for devices named MyDevice
)
.take(1) // stop the scan when a matching device will be scanned for the first time
.flatMap {
val device = it.bleDevice
device.establishConnection(false)
.flatMap < Any > {
rxBleConnection: RxBleConnection - > rxBleConnection.setupNotification(charUUID)
}
.doOnNext {
notificationObservable: Any ? - >
}
.flatMap < Any > {
notificationObservable: Any ? - > notificationObservable
} // <-- Notification has been set up, now observe value changes.
}
.subscribe(
{ notification -> Log.i("Notification!", notification.contentToString()) },
{ throwable -> Log.e("Whoops!", "Scan Error", throwable) }
)
I have a State(Enum) that contains (Good, Non-Critical, Critical) values
So requirement is :
should trigger when state goes in non-critical state.
should trigger when state goes in critical state.
should trigger when state stays in critical state for 15 seconds.
Input :
publishSubject.onNext("Good")
publishSubject.onNext("Critcal")
publishSubject.onNext("Critcal")
publishSubject.onNext("NonCritical")
publishSubject.onNext("Critacal")
publishSubject.onNext("Critical")
publishSubject.onNext("Good")
and so on...
See Code Structure for Reference:
var publishSubject = PublishSubject.create<State>()
publishSubject.onNext(stateObject)
publishSubject
/* Business Logic Required Here ?? */
.subscribeOn(Schedulers.computation())
.observeOn(AndroidSchedulers.mainThread())
.subscribe {
AppLogger.printLog("Trigger Success --> ")
}
Please help,
Thanks in Advance,
You can use distinctUntilChanged() to suppress events that don't change the state. Filter out the normal events using filter().
Use the switchMap() operator to create a new subscription when the state changes. When the state is "critical", use the interval() operator to wait out the 15 seconds. If the state changes in that 15 seconds, switchMap() will unsubscribe and re-subscribe to a new observable.
publishSubject
.distinctUntilChanged()
.subscribeOn(Schedulers.computation())
.observeOn(AndroidSchedulers.mainThread())
.filter( state -> state != State.Normal )
.switchMap( state -> {
if (state == State.Critical) {
return Observable.interval(0, 15, TimeUnit.SECONDS) // Note 1
.map(v -> State.Critical); // Note 2
}
return Observable.just( State.Noncritical );
})
.subscribe( ... );
interval() is given an initial value of 0, causing it to emit a value immediately. After 15 seconds, the next value will be emitted, and so on.
The map() operator turns the Long emitted by interval() into
The first two parts of your requirements should be combined into one. You're asking for the chain to be triggered on NonCritical and Critical events, ergo the chain should not be triggered for Good event. Likewise, you only need to trigger an event if the state is different from a previous event. For this two .filter events should suffice:
var lastKnownState: State = null
publishSubject
.subscribeOn(Schedulers.computation())
.observeOn(AndroidSchedulers.mainThread())
.filter(this::checkStateDiffers) // Check we have a new state
.filter { state -> state != State.Good } // Check event is good
.subscribe {
AppLogger.printLog("Trigger Success --> ")
}
...
private fun checkStateDiffers(val state: State): Boolean {
val isDifferent = state != lastKnownState
if (isDifferent) lastKnownState = state // Update known state if changed
return isDifferent
}
The timeout requirement is a bit trickier. RxJava's timeout() operator gives the option of emitting an error when nothing new has been received for a period of time. However I am assuming that you want to keep listening for events even after you receive a timeout. Likewise, if we just send another Critical event it'll be dropped by the first filter. So in this case I'd recommend a second disposable that just has the job of listening for this timeout.
Disposable timeoutDisp = publishSubject
.subscribeOn(Schedulers.computation())
.observeOn(AndroidSchedulers.mainThread())
.timeout(15, TimeUnit.SECONDS)
.onErrorResumeNext(State.Timeout)
.filter { state -> state == State.Timeout }
.filter { state -> lastKnownState == State.Critical }
.subscribe {
AppLogger.printLog("Timeout Success --> ")
}
Also adjust the checkStateDiffers() to not save this Timeout state in the first chain.
private fun checkStateDiffers(val state: State): Boolean {
if (state == State.Timeout) return true
var isDifferent = state != lastKnownState
if (isDifferent) lastKnownState = state // Update known state if changed
return isDifferent
}
I'm updating an app to use RxAndroidBLE, and struggling with how to translate my existing callback pattern into an Rx pattern. In particular, I need to respond to characteristic notifications in different ways depending on the received data, and send a specific write command back to the device (which will then cause the characteristic to be updated, in a loop).
The rationale behind this is that the BLE device I'm integrating with has a special custom characteristic, to which we can send different commands and then listen for data back.
I've read up lots about chaining commands using RxBLE, but none seem to address my particular query, which is how to send a command back to the device on observing a change notification (since the connection itself seems to be out of scope by the time we get to the observable block). What is the "Rx Way" of doing this?
For clarity, this is the entire flow of my BLE service:
scan for devices with a filter on our custom characteristic
connect to a found device
read a couple of standard characteristics (strings), and store these in our data model
if and only if one of the characteristics matches one of an array of strings, proceed to 5. otherwise, dispose of the connection.
subscribe to our custom "control" characteristic ("CC") for change notifications
send command 1 to CC. this should trigger answer 1 to be set in CC, so our handler is called
perform some calculations on answer 1 and save to our model. send command 2 (which includes these modified values, so we can't determine this at compile time) to CC. this should trigger answer 2 in CC.
on receiving answer 2, send command 3, which should trigger answer 3.
on reciving answer 3, parse into an int value. if answer 3 == 0, dispose of the connection - we are done.
answer 3 > 0, so send command 4. this will trigger answer 4.
perform some calculations on answer 4 and store the results in our model
then send command 5, which will actually trigger answer 3 (both commands 5 and 3 trigger answer 3). since we are already subscribed to answer 3, this takes us back to step 9. above - we keep looping until answer 3 is 0 (ie. we have saved all the data).
Edit: I was loathe to share code, as I'm well aware there is no possible way the following will work - but I'm hoping it describes what I'm trying to do even if the syntax won't even compile:
connectedDevice.connectionDisposable = connectedDevice.getRxDevice().establishConnection(false)
.observeOn(AndroidSchedulers.mainThread())
.flatMapSingle(rxBleConnection -> rxBleConnection.readCharacteristic(BATTERY_CHARACTERISTIC_UUID))
.doOnNext(bytes -> {
//store the battery info in our model here
})
.flatMapSingle(rxBleConnection -> rxBleConnection.readCharacteristic(SERIAL_NUMBER_CHARACTERISTIC_UUID))
.doOnNext(bytes -> {
//store the serial number info in our model here
//TODO: how do we only proceed to the subscription if serialNumber is correct?
}
)
.flatMap(rxBleConnection -> rxBleConnection.setupNotification(CUSTOM_CHARACTERISTIC_UUID))
.doOnNext(notificationObservable -> {
// Notification has been set up
rxBleConnection.writeCharacteristic(CUSTOM_CHARACTERISTIC_UUID, COMMAND_1); //we can't do this because rxBleConnection is out of scope!
})
.flatMap(notificationObservable -> notificationObservable) // <-- Notification has been set up, now observe value changes.
.subscribe(
bytes -> {
// Given characteristic has been changes, here is the value.
switch(commandFromBytes(bytes)){
case answer1:
int newCommand = doSomethingWith(bytes);
rxBleConnection.writeCharacteristic(CUSTOM_CHARACTERISTIC_UUID, COMMAND_2 + newCommand);
break;
case answer2:
rxBleConnection.writeCharacteristic(CUSTOM_CHARACTERISTIC_UUID, COMMAND_3);
break;
case answer3:
if(bytes <= 0){
connectedDevice.connectionDisposable.dispose();
}
else{
rxBleConnection.writeCharacteristic(CUSTOM_CHARACTERISTIC_UUID, COMMAND_4);
}
break;
case answer4:
doSomethingLongWindedWith(bytes);
//then
rxBleConnection.writeCharacteristic(CUSTOM_CHARACTERISTIC_UUID, COMMAND_5);
//command 5 will cause answer3 to be notified, so we loop back above
break;
}
},
throwable -> {
// Handle an error here.
}
);
Edit 2: after playing bracket tango for a bit, I think I'm close to a solution here:
connectedDevice.connectionDisposable = connectedDevice.getRxDevice().establishConnection(false)
.observeOn(AndroidSchedulers.mainThread())
.flatMapSingle(rxBleConnection -> rxBleConnection.readCharacteristic(BATTERY_CHARACTERISTIC_UUID)
.doOnNext(bytes -> {
connectedDevice.setBatLevel(bytes);
})
.flatMapSingle(rxBleConnection2 -> rxBleConnection.readCharacteristic(SERIAL_NUMBER_CHARACTERISTIC_UUID))
.doOnNext(bytes -> {
connectedDevice.setSerialNum(bytes);
//we also notify a singleton listener here
}
)
.flatMap(rxBleConnection3 -> {
if (serialNumberIsCorrect(connectedDevice.getSerialNum())) {
rxBleConnection.setupNotification(CUSTOM_CHARACTERISTIC_UUID).subscribe(
bytes -> {
// Given characteristic has been changes, here is the value.
switch (commandFromBytes(bytes)) {
case answer1:
int newCommand = doSomethingWith(bytes);
rxBleConnection.writeCharacteristic(CUSTOM_CHARACTERISTIC_UUID, COMMAND_2 + newCommand);
break;
case answer2:
rxBleConnection.writeCharacteristic(CUSTOM_CHARACTERISTIC_UUID, COMMAND_3);
break;
case answer3:
if (bytes <= 0) {
//we also notify a singleton listener here
connectedDevice.connectionDisposable.dispose();
} else {
rxBleConnection.writeCharacteristic(CUSTOM_CHARACTERISTIC_UUID, COMMAND_4);
}
break;
case answer4:
doSomethingLongWindedWith(bytes);
//then
rxBleConnection.writeCharacteristic(CUSTOM_CHARACTERISTIC_UUID, COMMAND_5);
//command 5 will cause answer3 to be notified, so we loop back above
break;
}
},
throwable -> {
// Handle an error here.
}
);
} else {
connectedDevice.connectionDisposable.dispose();
}
}
.doOnNext(notificationObservable -> {
// Notification has been set up
if (serialNumberIsCorrect(connectedDevice.getSerialNum())) {
rxBleConnection.writeCharacteristic(CUSTOM_CHARACTERISTIC_UUID, COMMAND_1);
}
})
));
The best approach, according to this Jake Wharton's talk would be to construct an Observable that would emit just values that are needed for updating your model.
(example in Kotlin)
We could have these outputs of the Observable:
sealed class ConnectionEvent {
object CloseConnection : ConnectionEvent() // dummy event to notify when the connection can be closed
data class SerialNumber(val byteArray: ByteArray) : ConnectionEvent()
data class BatteryLevel(val byteArray: ByteArray) : ConnectionEvent()
data class Answer4(val byteArray: ByteArray) : ConnectionEvent()
}
And the whole flow could look like this:
bleDevice.establishConnection(false)
.flatMap { connection ->
val batteryLevelSingle = connection.readCharacteristic(batteryLevelCharUuid).map { ConnectionEvent.BatteryLevel(it) as ConnectionEvent }
val serialNumberSingle = connection.readCharacteristic(serialNumberCharUuid).map { ConnectionEvent.SerialNumber(it) }.cache() // cache as the output will be used by the continuation observable as well and we do not want to re-read the serial number
val continuationObservable: Observable<ConnectionEvent> = serialNumberSingle // continuation observable will work if the serial number matches
.flatMapObservable {
when {
it != matchingSerialNumber -> Observable.just(ConnectionEvent.CloseConnection as ConnectionEvent) // close connection if serial does not match
else -> createContinuationObservable(connection) // create flow for getting more data via additional writes and notifications
}
}
Observable.concat( // the actual flow of the whole connection
batteryLevelSingle.toObservable(), // we are starting with getting the battery level and emitting it
serialNumberSingle.toObservable(), // we are getting the serial number and emitting it
continuationObservable // if the serial number matches we continue with notifications and getting more data. otherwise CloseConnection
)
}
.takeWhile { it != ConnectionEvent.CloseConnection } // if the connection is to be closed -> unsubscribe
.subscribe(
{ connectionEvent ->
when(connectionEvent) {
is ConnectionEvent.SerialNumber -> { /* Update model */ }
is ConnectionEvent.BatteryLevel -> { /* Update model */ }
is ConnectionEvent.Answer4 -> { /* Update model */ }
}
},
{ /* handle errors */ }
)
where the write/notification dance is:
private fun createContinuationObservable(connection: RxBleConnection): Observable<ConnectionEvent> {
return connection.setupNotification(customCharUuid)
.flatMap { ccNotifications ->
ccNotifications.flatMap {
when (answerFromBytes(it)) {
answer1 -> connection.writeCharacteristic(customCharUuid, command2FromAnswer1Bytes(it)).ignoreEmissions()
answer2 -> connection.writeCharacteristic(customCharUuid, command3).ignoreEmissions()
answer3 -> when (it.isEmpty()) {
true -> Observable.just(ConnectionEvent.CloseConnection)
else -> connection.writeCharacteristic(customCharUuid, command4).ignoreEmissions()
}
answer4 -> connection.writeCharacteristic(customCharUuid, command5).ignoreEmissions()
.startWith(Observable.just(ConnectionEvent.Answer4(it)))
else -> Observable.error(Exception("Unexpected answer! => ${answerFromBytes(it)}"))
}
}
.startWith(connection.writeCharacteristic(customCharUuid, command1).ignoreEmissions()) // initiate with the command1
}
}
I have used an extension function for more clarity:
fun Single<ByteArray>.ignoreEmissions() = this.toCompletable().toObservable<ConnectionEvent>()
Edit:
I have changed the code a bit to get rid of CloseConnection event and leverage the completions of the observables. So now the outputs look like this:
sealed class ConnectionEvent {
data class SerialNumber(val byteArray: ByteArray) : ConnectionEvent()
data class BatteryLevel(val byteArray: ByteArray) : ConnectionEvent()
data class Answer4(val byteArray: ByteArray) : ConnectionEvent()
}
The main flow:
bleDevice.establishConnection(false)
.map { connection ->
val batteryLevelSingle = connection.readCharacteristic(batteryLevelCharUuid).map { ConnectionEvent.BatteryLevel(it) as ConnectionEvent }
val serialNumberSingle = connection.readCharacteristic(serialNumberCharUuid).map { ConnectionEvent.SerialNumber(it) }.cache() // cache as the output will be used by the continuation observable as well and we do not want to re-read the serial number
val continuationObservable: Observable<ConnectionEvent> = serialNumberSingle // continuation observable will work if the serial number matches
.flatMapObservable {
if (it == matchingSerialNumber) createContinuationObservable(connection) // create flow for getting more data via additional writes and notifications
else Observable.empty() // do not continue if serial number does not match
}
Observable.concat( // the actual flow of the whole connection
batteryLevelSingle.toObservable(), // we are starting with getting the battery level and emitting it
serialNumberSingle.toObservable(), // we are getting the serial number and emitting it
continuationObservable // if the serial number matches we continue with notifications and getting more data. otherwise CloseConnection
)
}
.publish {
// create a Completable from the above Observable.concat()
val dataDownloadCompletable = it.take(1) // take the first emission (there will be only one)
.flatMapCompletable { it.ignoreElements() } // and wait until the first emission completes
it.takeUntil(dataDownloadCompletable.toObservable<Any>()) // when dataDownloadCompletable completes —> unsubscribe from the upstream, mainly .establishConnection() to close it
}
.flatMap { it } // unwrap the above flow
.subscribe(
{ connectionEvent ->
when (connectionEvent) {
is ConnectionEvent.SerialNumber -> { /* Update model */ }
is ConnectionEvent.BatteryLevel -> { /* Update model */ }
is ConnectionEvent.Answer4 -> { /* Update model */ }
}
},
{ /* handle errors */ }
)
Write/notification part:
private fun createContinuationObservable(connection: RxBleConnection): Observable<ConnectionEvent> {
return connection.setupNotification(customCharUuid)
.flatMap { ccNotifications ->
ccNotifications.map { Pair(answerFromBytes(it), it) } // map every response to a pair of <answer, bytes>
.startWith(connection.writeCharacteristic(customCharUuid, command1).ignoreEmissions()) // and start with writing command1 to initiate the data exchange
}
.takeWhile { (answer, bytes) -> !(answer == answer3 && bytes.isEmpty()) } // end the createContinuationObservable on the first answer3 with an empty bytes
.flatMap<ConnectionEvent> { (answer, bytes) ->
when (answer) {
answer1 -> connection.writeCharacteristic(customCharUuid, command2FromAnswer1Bytes(bytes)).ignoreEmissions()
answer2 -> connection.writeCharacteristic(customCharUuid, command3).ignoreEmissions()
answer3 -> connection.writeCharacteristic(customCharUuid, command4).ignoreEmissions()
answer4 -> Observable.just(ConnectionEvent.Answer4(bytes)) // when answer4 is received emit actionable item to update the model
.concatWith(connection.writeCharacteristic(customCharUuid, command5).ignoreEmissions()) // and send the next command5
else -> Observable.error(Exception("Unexpected answer! => $answer"))
}
}
}
And the extension:
fun <T> Single<ByteArray>.ignoreEmissions() = this.toCompletable().toObservable<T>()
I have an edit text which allows a user to input a username and once the username is input the value is sent to the db to check whether the username already exists,if not then further operations are allowed else an error is displayed.
As of now this is my current code.
usernameObservable
.skip(2)
.debounce(800, TimeUnit.MILLISECONDS)
.subscribe(username -> {
Observable<Boolean> observable = apiService.isAvailable(username);
observable.observeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(aBoolean -> {
}, throwable -> {
});
});
For now the network request is being made at the end but is it possible to make the request before and once i receive data i perform some other operations on the stream.
You are looking for flatMap operator. It allows you to transform an event into another observable, which will forward emissions to the original stream. You error notification will be forwarded as well.
usernameObservable
.skip(2)
.debounce(800, TimeUnit.MILLISECONDS)
.flatMap(username -> apiService.isAvailable(username))
.subscribe(isAvailableResult -> {
// react here
}, throwable -> {
// show an error here
});