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
});
Related
I have a webservice call that return an object in which there is a parameter that indicates whether the operation ended successfully or not, so I would like to filter it (kind of if/else statement) inside the RxJava chain by using RxJava operators. Is it possible?
Something like this but not using if/else:
repo.webserviceCall(username, password)
.flatMap(result -> {
if (result.isSuccessful())
repo.secondWebserviceCall(result.getInfo())
else
showToastMessage("Api call not successful"); //STOP FLOW HERE
})
.flatMap(result -> thirdWebserviceCall(res))
.subscribe(res -> {showSuccessMssg(res)}, throwable -> { showError(t)});
You can return an error() from your flatMap so that the execution then goes to the onError consumer in your subscribe call.
If each service call returns one item, you could rearrange the operators so that not successful won't run the flatMap for the second and third calls. The filter will turn the setup to empty for which you can use the onComplete handler to display the toast.
repo.webserviceCall(username, password)
.filter(result -> result.isSuccessful())
.flatMap(result ->
repo.secondWebserviceCall(result.getInfo())
.flatMap(result -> thirdWebserviceCall(res))
)
.subscribe(
res -> showSuccessMssg(res),
throwable -> showError(t),
() -> showToastMessage("Api call not successful")
);
I am trying to chain two network calls in my Android app. I am using Retrofit. Basically I want to do :
Make API Call to login
Wait for the response of login, save the token to SharedPrefs
Make another API call right after I've saved the token
Wait for the response, save the data
I think I have chained the stream in the right way, the only thing is I want to update the UI in between. For example once the call starts I want to display a progressDialog ( I do that in doOnSubscribe ), or dismiss the Dialog once the call has completed ( I do that in doOnComplete ). However I get the exception Only the original thread that created a view hierarchy can touch its views. I subscribe on the io thread and observe on the mainThread so that I can make the changes to the UI, however I must be missing something.
I tried adding .subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
lower in the stream, but I still get the same error message.
getView().onLoginAction().subscribe(aVoid -> Observable.combineLatest(
getView().userNameObservable().map(CharSequence::toString),
getView().passwordObservable().map(CharSequence::toString),
Pair::new)
.first()
.subscribe(usernamePasswordPair -> {
User user = User.create(usernamePasswordPair.first, usernamePasswordPair.second, "");
RetrofitClientInstance.createService(AuthenticationNetworkApi.class).login(new Login(user.username(), user.password()))
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.doOnNext(loginResponse -> {
AuthorizationResponse responseBody = loginResponse.body();
if (responseBody != null && responseBody.getAccessToken() != null && !responseBody.getAccessToken().isEmpty()) {
if (localStorage.getAccessToken().isEmpty()) {
localStorage.saveAccessToken(responseBody.getAccessToken());
}
}
}
).
doOnSubscribe( action -> getView().showProgressDialog())
.doOnError(error -> {
getView().dismissProgressDialog();
getView().showErrorMessage("Login Unsuccessful");
}).doOnComplete(() -> getView().dismissProgressDialog()
)
.flatMap(response -> RetrofitClientInstance.createService(ActivitiesApi.class).getUserActivities())
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.doOnNext(activities -> {
for (UserActivityApiModel useractivity : activities
) {
activityService.addActivity(Activity.create(Integer.parseInt(useractivity.getId()), useractivity.getActivityName(), useractivity.getDate(),
Integer.parseInt(useractivity.getValue()), Integer.parseInt(useractivity.getSubCategory().getId())));
}
}).doOnError(error -> getView().showErrorMessage(error.getMessage()))
.doOnComplete(() -> getView().redirectToHomeScreen())
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe();
}));
The error occurs here :
.doOnError(error -> {
getView().dismissProgressDialog();
getView().showErrorMessage("Login Unsuccessful");
})
It seems you are using a different thread to execute your backend. In that case, you can't touch the main UI thread from the second one. You need to execute first runOnUiThread { //your code }
In //your code, call the two lines of code that you put on doOnError.
I'm using a travel API which I first hit with a request to create a session and then I use the session URL returned from that URL to then call until its status parameter returns UpdatesComplete.
Here's what I have so far:
lateinit var pollUrl: String
travelInteractor.createSession("LHR", "AKL", "2018-04-20", "2018-04-22")
.doOnSubscribe {
loading.postValue(true)
}
.flatMap { url ->
pollUrl = url
travelInteractor.pollResults(pollUrl)
.retryWhen {
it.delay(1000, TimeUnit.MILLISECONDS)
}
}
.doOnNext {
if (it.status != "UpdatesComplete") travelInteractor.pollResults(pollUrl)
.retryWhen {
it.delay(1000, TimeUnit.MILLISECONDS)
}
}
.subscribe({
// Subscription stuff
)}
What's currently happening is that it will call doOnNext() and then it will make the network poll but I won't be capturing the subscription and also won't chain another poll. Is there a more efficient way I can be writing this?
Solution
Thanks to iagreen I managed to achieve this with:
lateinit var pollUrl: String
travelInteractor.createSession("LHR", "AKL", "2018-04-20", "2018-04-22")
.doOnSubscribe {
loading.postValue(true)
}
.flatMap { url ->
travelInteractor.pollResults(url)
.retryWhen {
it.delay(1000, TimeUnit.MILLISECONDS)
}
.repeatWhen {
it.delay(1000, TimeUnit.MILLISECONDS)
}
.filter {
it.itineraries.map { ... } // Use response here appropriately and then check status
it.status == "UpdatesComplete"
}
.take(1)
}
.subscribe({
// Subscription stuff
)}
I am assuming your pollResults(url) method returns a Single or an Observable that behaves like a single -- it returns one result and then onComplete. If that is truly the case, you can use repeatWhen to retry the request on success and retryWhen to retry on error. See the code below.
skyScannerInteractor.createSession("LHR", "AKL", "2018-04-20", "2018-04-22")
.doOnSubscribe {
loading.postValue(true)
}
.flatMap { url ->
skyScannerInteractor.pollResults(url)
.repeatWhen { complete -> complete.delay(1, TimeUnit.SECONDS) }
.retryWhen { errors -> errors.delay(1, TimeUnit.SECONDS) }
.filter({ it.status == "UpdatesComplete" })
.take(1) // Take the first valid value and complete
}
.subscribe({
// Subscription stuff
)}
A little explanation -
repeatWhen/retryWhen will try the request every second.
filter will cause elements with the wrong status to be ignored.
When you get the first status == "UpdatesComplete" element, take(1) will emit that value and complete -- this will have the effect of cancelling the retries.
Note: In the case of an error, retrying a network request forever is usually the wrong thing to do. I recommend you modify the retryWhen above to suit you use case to terminate in the event of network failure. For example, you could retry three times and then propagate the error. See this article for some examples on how you could do that. It is also a good reference on repeatWhen/retryWhen.
retry() and retryWhen() both respond to an onError event in the Observable, that is why it isn't actually retrying; you're not receiving those onError events in your pollResults() Observable. Right now, your retrying code doesn't actually depend on the JSON response.
There are two ways I would think about going about this:
Throw an exception in your pollResults() Observable if the JSON response is unsatisfactory. This should trigger the retryWhen(). You'll need to test for it in the Observable somewhere.
Reorganize your Observable like this:
`
lateinit var pollUrl: String
skyScannerInteractor.createSession("LHR", "AKL", "2018-04-20", "2018-04-22")
.doOnSubscribe {
loading.postValue(true)
}
.flatMap { url ->
pollUrl = url
skyScannerInteractor.pollResults(pollUrl)
}
.doOnNext {
if (it.status != "UpdatesComplete") {
throw IOException("Updates not complete.") //Trigger onError
}
}
.retryWhen { //Retry the Observable (createSession) when onError is called
it.delay(1000, TimeUnit.MILLISECONDS)
}
.subscribe({
// Will give result only when UpdatesComplete
}
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>()
My Question is probably more of the conceptual nature.
I get that by the Observable contract my Observable will not emit any more items after onComplete or onError is called.
But I'm using the RxBindings for Android and therefore it's not "my Observable" but the click on a Button that emits items.
fun observeForgotPasswordButton(): Disposable {
return view.observeForgotPasswordButton()
.flatMap {
authService.forgotPassword(email).toObservable<Any>()
}
.subscribe({
// on next
Timber.d("fun: onNext:")
}, { error ->
// on error
Timber.e(error, "fun: onError")
}, {
// onComplete
Timber.d("fun: onComplete")
})
}
observeForgotPasswordButton() returns an Observable
fun observeForgotPasswordButton(): Observable<Any> = RxView.clicks(b_forgot_password)
The problem is that authService.forgotPassword(email) is a Completable and it will call either onComplete or onError both of which lead to the fact that I cannot reuse the button anymore since the subscription ended.
Is there a way to circumvent this behavior?
Because in an error occurs I would like to be able to retry.
Also I would like it to be possible to send more then one password forgotten emails.
You can use the retry() and repeat() operators to automatically resubscribe to the original Observable (or Completable).
fun observeForgotPasswordButton(): Disposable {
return view.observeForgotPasswordButton()
.flatMap {
authService.forgotPassword(email).toObservable<Any>()
}
.repeat() // automatically resubscribe on completion
.retry() // automatically resubscribe on error
.subscribe({
// on next
Timber.d("fun: onNext:")
}, { error ->
// on error
Timber.e(error, "fun: onError")
}, {
// onComplete
Timber.d("fun: onComplete")
})
}