I'm introducing myself about RxJava2, but i feel like i'm doing something wrong. In my case, i want to do some following asynchronous actions.
In this example, the first action is to check if the device is connected (wifi or data, let's admit it take time), then i want to connect to an api and then i want to do a http call for get a list (observable) and then work with it. If one of those operation fail, an onError or exception should be raised and handled in the subscribe.
I have this code who works:
Single.create((SingleEmitter<Boolean> e) -> e.onSuccess(Connectivity.isDeviceConnected(MainActivity.this)) )
.subscribeOn(Schedulers.io())
.flatMap(isDeviceConnected -> {
Log.i("LOG", "isDeviceConnected : "+ isDeviceConnected);
if(!isDeviceConnected)
throw new Exception("whatever"); // TODO : Chercher vrai erreur
return awRepository.getFluxAuthenticate(host, port, user, password); // Single<DisfeApiAirWatch>
})
.toObservable()
.flatMap(awRepository::getFluxManagedApps) // List of apps : Observable<AirwatchApp>
.observeOn(AndroidSchedulers.mainThread())
.doFinally(this::hideProgressDialog)
.subscribe(
app -> Log.i("LOG", "OnNext : "+ app),
error -> Log.i("LOG", "Error : " + error),
() -> Log.i("LOG", "Complete : ")
);
But do a single who emmit a boolean for a simple "if" sounds wrong. A Completable seems more logical (work or not, continue or stop). I tried with the following code but it's not working.
Completable.create((CompletableEmitter e) -> {
if(Connectivity.isDeviceConnected(MainActivity.this))
e.onComplete(); // Guess not good, should call the complete of subscribe ?
else
e.onError(new Exception("whatever"));
} ).toObservable()
.subscribeOn(Schedulers.io())
.flatMap(awRepository.getFluxAuthenticate(host, port, user, password)) //Single<DisfeApiAirWatch>
.toObservable()
.flatMap(awRepository::getFluxManagedApps) // List of apps : Observable<AirwatchApp>
.observeOn(AndroidSchedulers.mainThread())
.doFinally(this::hideProgressDialog)
.subscribe(
app -> Log.i("LOG", "OnNext : "+ app),
error -> Log.i("LOG", "Error : " + error),
() -> Log.i("LOG", "Complete : ")
);
How to make this code work ?
I know i can do a first subscribe on the complatable and in the "onSuccess" of this one write another flux / the rest of the code. But i don't think stack flows inside each other is a good solution.
Best regards
Completable has no value so flatMap will never be invoked. You have to use andThen and make the authentication success value the input for the subsequent flatMap:
Completable.create((CompletableEmitter e) -> {
if(Connectivity.isDeviceConnected(MainActivity.this))
e.onComplete();
else
e.onError(new Exception("whatever"));
})
.subscribeOn(Schedulers.io())
.andThen(awRepository.getFluxAuthenticate(host, port, user, password)) // <-----------
.flatMapObservable(awRepository::getFluxManagedApps)
.observeOn(AndroidSchedulers.mainThread())
.doFinally(this::hideProgressDialog)
.subscribe(
app -> Log.i("LOG", "OnNext : "+ app),
error -> Log.i("LOG", "Error : " + error),
() -> Log.i("LOG", "Complete : ")
);
Related
I've been having troubles with subscribe() method in my code (debug console's message below)
io.reactivex.exceptions.OnErrorNotImplementedException: The exception was not handled due to missing onError handler in the subscribe() method call. Further reading: https://github.com/ReactiveX/RxJava/wiki/Error-Handling | Expected a string but was BEGIN_OBJECT at line 1 column 2 path $
and I can't figure out how to make it right, there is my part of code where it starts
private fun startSearch(query: String) {
disposables.addAll(IMyService.searchCourse(query)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe ({ courses ->
adapter = CourseAdapter(baseContext, courses)
recycler_search.adapter = adapter
}, {
Toast.makeText(this, "Not found", Toast.LENGTH_LONG).show()
}))
}
private fun getAllCourses() {
disposables.addAll(IMyService.coursesList
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe ({ courses ->
adapter = CourseAdapter(baseContext, courses)
recycler_search.adapter = adapter
}, {
Toast.makeText(this, "Not found", Toast.LENGTH_LONG).show()
}))
}
and there is the full code
parameters
In reactive programming, passing a subscriber to an Observable should entail how to deal with three cases:
onSuccess
onError
onFailure
If however, you simply want to pass a subscriber which you know for sure will not have any errors or any failures and certain that it will always succeed, then simply try onSuccess or onFailure as mentioned by #EpicPandaForce. A good practice however is to always implement the three cases as you never know.
I'm trying to listen to whether my app is connected to a bluetooth device. I'm trying to print the connectionState result but the application is not even reaching the first println so I can't check what they may be. I want to enumerate the possible connection states, and then to adjust the UI in response. How can I do this?
val rxBleClient = RxBleClient.create(this.context!!)
val bleDevice = rxBleClient.getBleDevice("34:81:F4:3C:2D:7B")
val disposable = bleDevice.establishConnection(true) // <-- autoConnect flag
.subscribe({
rxBleConnection ->
// All GATT operations are done through the rxBleConnection.
bleDevice.observeConnectionStateChanges()
.subscribe({
connectionState ->
println("Connection State: $connectionState")
if (connectionState != null) {
enableBluetooth.setBackgroundResource(R.drawable.bluetooth_on) // Change image
deviceConnected.setText(R.string.connected_to_hooplight) // Changed text
} else {
enableBluetooth.setBackgroundResource(R.drawable.bluetooth_off) // Change image
deviceConnected.setText(R.string.connect_to_hooplight) // Changed text
}
}, {
throwable ->
Log.d("Error: ", throwable.toString())
})
}, {
throwable ->
// Handle an error here.
Log.d("Error: ", throwable.toString())
})
// When done... dispose and forget about connection teardown :)
disposable.dispose()
There are two things to the above code:
disposable.dispose() should be called when the flow that was subscribed is no longer needed. If the dispose method is called right after subscription nothing will actually happen. That is why even the first println does not show up.
The bleDevice.establishConnection() and bleDevice.observeConnectionStateChanges() are not dependent on each other functionally. Connection does not have to be established to observe changes. Even if one would start observing the changes after the connection is on it will only get info when the connection is closed (because it is the first change since then)
A better way would be to decouple the observing connection changes flow and the actual connection. An example code:
val observingConnectionStateDisposable = bleDevice.observeConnectionStateChanges()
.subscribe(
{ connectionState ->
Log.d("Connection State: $connectionState")
if (connectionState == RxBleConnectionState.CONNECTED) { // fixed the check
enableBluetooth.setBackgroundResource(R.drawable.bluetooth_on) // Change image
deviceConnected.setText(R.string.connected_to_hooplight) // Changed text
} else {
enableBluetooth.setBackgroundResource(R.drawable.bluetooth_off) // Change image
deviceConnected.setText(R.string.connect_to_hooplight) // Changed text
}
},
{ throwable -> Log.d("Error: ", throwable.toString()) }
)
val connectionDisposable = bleDevice.establishConnection(false)
.subscribe(
{ Log.d("connection established") }, // do your thing with the connection
{ throwable -> Log.d("Error on connection: ${throwable}") }
)
I'm trying to understand retryWhen operator in depth and I have some code as below.
Flowable.just(1, 2, 3, 4, 5)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.retryWhen { throwable ->
Log.d("Debug", "retryWhen proceed...")
throw Exception("There is a exception")
}
.subscribe(
{ item ->
Log.d("Debug", "success : $item")
},
{ throwable ->
Log.d("Debug", "error : ${throwable.message}")
},
{
Log.d("Debug", "complete")
}
)
And the result is shwon as below.
Debug: retryWhen proceed...
Debug: error : There is a exception
The question is that when retryWhen operator is triggered?
I assume retryWhen operator will be triggered only when there is a exception occurs.
But the result is not what I thought obviously,
Any thoughts on this? Thanks!
retryWhen { errors -> ... } take an Observable<Throwable> and should return an Observable that return anything for retrying or an error for stop retrying.
One example could be:
.retryWhen(attempts -> {
return attempts.zipWith(Observable.range(1, 3), (n, i) -> i).flatMap(i -> {
System.out.println("delay retry by " + i + " second(s)");
return Observable.timer(i, TimeUnit.SECONDS);
});
})
(taken from http://reactivex.io/documentation/operators/retry.html)
This code will delay each retry.
By the way, throwing an exception is not the thing to do in this method.
Documentation:
* Great blog article that explained the retryWhen
I am struggling with correct exception handling in RxJava2. I am trying to refresh my token inside an OkHttp3 interceptor like this:
tokenRepository.refreshToken(authStateManager.current.refreshToken!!)
.doOnError {
Log.w(TAG, "Could not obtain new access token.")
authStateManager.signOut()
}
.subscribe { tokenResponse ->
Log.d(TAG, "Obtained new access token: " + tokenResponse.toString())
authStateManager.updateAfterTokenResponse(TokenResponse.jsonDeserialize(tokenResponse.toString()), null)
token = authStateManager.current.accessToken
}
It works nicely if the refresh token is valid and the request returns 200. But if the refresh token is invalidated and I get some error code (for example 400), the doOnError block is executed but it then proceeds to the subscribe block where the following exception is thrown:
retrofit2.adapter.rxjava2.HttpException: HTTP 400
at retrofit2.adapter.rxjava2.BodyObservable$BodyObserver.onNext(BodyObservable.java:54)
at retrofit2.adapter.rxjava2.BodyObservable$BodyObserver.onNext(BodyObservable.java:37)
...
I tried using onErrorReturn and onErrorResumeNext but I would like to skip the subscribe block completely (it there is an error, just log user out and don't try to do anything). Is it possible? The refreshToken method returns Single<JsonObject> type of response.
Can't you use the onError in the subscription? I like using RxKotlin for this. For example, I can do the following:
.subscribeBy(onSuccess = {
// do Something in on success
}, onError = {
// do something in onError
})
So in your case, it could be something like:
tokenRepository.refreshToken(authStateManager.current.refreshToken!!)
.subscribeBy(onSuccess = {
Log.d(TAG, "Obtained new access token: " + it.toString())
authStateManager.updateAfterTokenResponse(TokenResponse.jsonDeserialize(it.toString()), null)
token = authStateManager.current.accessToken
}, onError = {
Log.w(TAG, "Could not obtain new access token.")
// the variable "it" here is a throwable which means you can determine if it's a RetrofitException and what status code is returned
})
However, if you do not want to use RxKotlin, you can handle your Rx subscriptions like this in Kotlin:
.subscribe({ Log.d(TAG, "Obtained new access token: " + it.toString()) },
{ Log.e(TAG, "Error is ${it.message}") })
You can handle the error in the second part of the function where the Log.e is being used.
I just had the same problem but I fixed it now. I don't know if my explanation is correct but it seems like doOnError doesn't handle the exception.
You need to use the subscribe() method that takes an onError Consumer as a parameter and handle the error there:
subscribe(Consumer<? super T> onSuccess, Consumer<? super Throwable> onError)
In Java it would look like this:
.subscribe(tokenResponse -> {
Log.d(TAG, "Obtained new access token: " + tokenResponse.toString());
authStateManager.updateAfterTokenResponse(TokenResponse.jsonDeserialize(tokenResponse.toString()), null);
token = authStateManager.current.accessToken;
}, throwable -> {
Log.w(TAG, "Could not obtain new access token.");
authStateManager.signOut();
}));
While I'm using debounce() ,then fetch data from backend and the data
I want to convert to another data and lastly use toList().
when I'm using toList() nothing happens no any log not in subscribe and error ,without toList() it works and subscribe() method enters as much as I have list of books, I tested the second part of code it without debounce() just getItems() and using toList() it works.
Below is my code the first part with debounce() and itList() which is not working and the second with toList() which works
public Flowable<List<Book>> getItems(String query) {}
textChangeSubscriber
.debounce(300, TimeUnit.MILLISECONDS)
.observeOn(Schedulers.computation())
.switchMap(s -> getItems(s).toObservable())
.flatMapIterable(items -> items)
.map(Book::convert)
.toList()
.observeOn(AndroidSchedulers.mainThread())
.subscribe(books -> {
Log.i("test", "" + books.toString());
}, error -> {
Log.i("test", "" + error);
});
getItems(query).flatMapIterable(items -> items)
.map(Book::convert)
.toList()
.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.io())
.subscribe(books -> {
Log.i("test", "" + "" + books.toString());
}, error -> {
Log.i("test", "" + error);
});
toList requires the sequence to terminate which doesn't happen on the outer stream that responds to text events. You should move the processing of the books into the switchMap:
textChangeSubscriber
.map(CharSequence::toString) // <-- text components emit mutable CharSequence
.debounce(300, TimeUnit.MILLISECONDS)
.observeOn(Schedulers.computation())
.switchMap(s ->
getItems(s)
.flatMapIterable(items -> items)
.map(Book::convert)
.toList()
.toFlowable() // or toObservable(), depending on textChangeSubscriber
)
.observeOn(AndroidSchedulers.mainThread())
.subscribe(books -> {
Log.i("test", "" + books.toString());
}, error -> {
Log.i("test", "" + error);
});