I am developing an Android app using RxJava.
I have some API call chains.
verify
consume
val verify = Completable.error(Exception("TEST"))
.doOnSubscribe { Log.d(TAG, "1. verify") }
.doOnComplete{ Log.d(TAG, "1. verify - success") }
.doOnError { Log.e(TAG, "1. verify - failed: ${it.message}") }
.retryWhen { attempts ->
attempts.zipWith(
Flowable.range(1, 3), BiFunction<Throwable, Int, Long> { t, i ->
if (i <= 3) {
1L
} else {
throw t
}
}
).flatMap {
Flowable.timer(it, TimeUnit.SECONDS)
}
}
// 2. consume
val consume = Single.just("SUCCESS")
.doOnSubscribe { Log.d(TAG, "2. consume") }
.doOnSuccess { Log.d(TAG, "2. consume - success") }
.doOnError { Log.e(TAG, "2. consume - failed: ${it.message}", it) }
disposable.add(
verify.andThen (consume)
.subscribeOn(ioScheduler)
.observeOn(uiScheduler)
.subscribe({
Log.d(TAG, "done")
}, { t ->
Log.e(TAG, "failed: ${t.message}", t)
})
);
What I excepted is...
"verify" should be called 3 times every 1 seconds.
After 3 retries failed, it should be done with Error.
But in my case, "consume" was run too.
Why?
I want to skip "consume" if "verify" is failed!
How can I do it?
It's because your code is not failing.
With Flowable.range(1, 3) you create a range from 1 to 3 so the else part of your code is never reached.
Try with Flowable.range(1, 4) and you will see the correct behaviour.
Related
I'm making network requests and when there is an error like internet is offline, then it should show user an error, but retry in background so when user gets access to internet it automatically fetches data.
I have following code, which return error after retry, but I need to return error immediatelly, but don't have any clue how to do it. Can anybody help? Thanks in advance.
apiService.getForecastWeatherByLocation(latitude, longitude)
.subscribeOn(Schedulers.io()).observeOn(Schedulers.io()).map { response ->
if (response.isSuccessful) {
Resource.success(
transformForecastResponseToForecast(response.body())
)
} else {
Resource.error(response.code(), response.message())
}
}
.startWith(Resource.loading(null))
.retryWhen { errors: Flowable<Throwable> ->
errors.zipWith(
Flowable.range(1, 3 + 1),
BiFunction<Throwable, Int, Int> { error: Throwable, retryCount: Int ->
if (retryCount > 3) {
throw error
} else {
retryCount
}
}
).flatMap { retryCount: Int ->
Flowable.timer(
2.toDouble().pow(retryCount.toDouble()).toLong(),
TimeUnit.SECONDS
)
}
}.onErrorReturn {
Resource.error(AppConstants.UNKNOWN_ERROR, it.localizedMessage ?: "")
}
I don't think it is feasible in single stream to achieve what you want, as a stream closes once onError() is called. How about a workaround?
val retryObservable = apiService.getForecastWeatherByLocation(...)
.map { ... }
.subscribeOn(...)
.observeOn(...)
.retryWhen(...)
apiService.getForecastWeatherByLocation(...)
.map { ... }
.subscribeOn(...)
.observeOn(...)
.startWith(...)
.onErrorReturn {
retryObservable.subscribe()
Resource.error(...)
}
.subscribe()
I'm trying to retrieve a value from a Bluetooth Device.
if (rxBleDevice.connectionState != RxBleConnection.RxBleConnectionState.CONNECTED) {
rxBleDevice!!.establishConnection(false) ? .subscribe({
rxBleConnection ->
Log.d("Device: ", "Connection Established")
val stringDeviceUUID = rxBleDevice.bluetoothDevice.uuids[0].toString()
val charUUID = UUID.nameUUIDFromBytes(stringDeviceUUID.toByteArray())
val count = rxBleConnection.readCharacteristic(charUUID)
println("OUTPUT: ${count}")
}, {
throwable -> Log.d("Device: ", "$throwable")
})
}
I'm using the following dependencies, mainly RxJava and a reactive Bluetooth library called RxAndroidBLE:
implementation 'io.reactivex.rxjava2:rxkotlin:2.1.0'
implementation "com.polidea.rxandroidble2:rxandroidble:1.8.1"
implementation "io.reactivex.rxjava2:rxjava:2.2.7"
My output:
I/System.out: OUTPUT: io.reactivex.internal.operators.single.SingleFlatMap#bf9162d
I have no how to process this object. I believe I should be receiving a simple ByteArray from the Bluetooth device.
An example for the value I should see is datc00099, indicating a count of 99.
You are supposed to subscribe to Single. Following the examples provided by RxAndroidBle, something like this might work in your case:
if (rxBleDevice.connectionState != RxBleConnection.RxBleConnectionState.CONNECTED) {
// Have your charUUID ready. Might need extra null checks for rxBleDevice
val charUUID = rxBleDevice.bluetoothDevice.uuids[0].uuid
rxBleDevice!!.establishConnection(false) ?
.doOnNext {
_ -> Log.d("Device: ", "Connection Established")
} ?
.flatMapSingle {
rxBleConnection -> rxBleConnection.readCharacteristic(charUUID)
} ? .subscribe({
count ->
// count should be in bytes
println("OUTPUT: $count")
}, {
throwable ->
Log.d("ERROR: ", "$throwable")
})
}
Hello guys I have in my BaseActivity the following function.
override fun <T> subscribeToInternet(observable: Observable<Response<BaseResponse<T>>>, observer: Observer<BaseResponse<T>>) {
observable.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.doOnSubscribe { observer.onSubscribe(it) }
.doOnError {
Log.d(TAG, it.message)
observer.onError(it)
}
.doOnComplete { observer.onComplete() }
.doOnNext {
Log.d(TAG, "${it.body() ?: "no body"}")
Log.d(TAG, "${it.errorBody()?.string() ?: "no error body"}")
Log.d(TAG, it.code().toString())
when {
it.code() == 401 -> {
view.userUnauthenticated()
observer.onNext(BaseResponse(false, "unauthenticated", null))
Log.d(TAG, "UNAUTHENTICATED")
}
it.code() == 423 -> {
view.userBlocked()
observer.onNext(BaseResponse(false, "blocked", null))
Log.d(TAG, "BLOCKED")
}
it.isSuccessful -> observer.onNext(it.body()!!)
it.code() == 429 -> observer.onNext(BaseResponse(false, "Too many attempts", null))
it.code() == 400 -> observer.onNext(BaseResponse(false, "Invalid Email or password", null))
else -> observer.onNext(BaseResponse(false, "", null))
}
}
.subscribe()
}
And I handle the error in the observer's onNext() if the server returns a response, but the problem when there's no Internet connection on the device at all!! It throws the following exception
at io.reactivex.internal.operators.observable.ObservableDoOnEach$DoOnEachObserver.onError(ObservableDoOnEach.java:119)
at io.reactivex.internal.observers.DisposableLambdaObserver.onError(DisposableLambdaObserver.java:64)
at io.reactivex.internal.operators.observable.ObservableObserveOn$ObserveOnObserver.checkTerminated(ObservableObserveOn.java:276)
at io.reactivex.internal.operators.observable.ObservableObserveOn$ObserveOnObserver.drainNormal(ObservableObserveOn.java:172)
at io.reactivex.internal.operators.observable.ObservableObserveOn$ObserveOnObserver.run(ObservableObserveOn.java:252)
And this is the usage of the previously mentioned function
override fun sendLoginRequest(email: String, password: String, fcm_token: String) {
subscribeToInternet(dataManager.sendLoginRequest(email, password, fcm_token), this)
}
override fun onComplete() {
}
override fun onSubscribe(d: Disposable) {
DisposableManager.add(d)
}
override fun onNext(t: BaseResponse<LoginData>) {
if(t.status) {
Log.d(TAG, "${t.data}")
dataManager.createLoginSession(t.data!!)
view.loginSuccess()
} else {
Log.d(TAG, t.message)
view.showError(t.message)
}
}
override fun onError(e: Throwable) {
view.showToastError()
Log.d(TAG, e.message)
}
That problem is connected with the way you subscribing to observable. According to
documentation when using subscribe() without passing action for handling errors, you should receive OnErrorNotImplementedException when source throws exceptions - that's because default exception handler from RxJavaPlugins is used.
To resolve that problem use one of overloaded subscribe methods with onError parameter. For example, public final Disposable subscribe(Consumer onNext,
Consumer onError)
I have these two Observables in Kotlin where is just act as a timer and another one is HTTP network call response Observer.
timerDisposable = Observable.timer(daleyABCControlResetSeconds, TimeUnit.SECONDS, AndroidSchedulers.mainThread())
.doOnNext {
if (getABCUpdate() != null) {
Log.d("ABC", "Media status reset after 3 seconds: ")
updateABCResponse(getABCUpdate())
}
}.subscribe()
disposable = audioApi.setABCUpdate(abcUpdate)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe({
timerDisposable.dispose()
updateABCResponse(it)
Log.d("ABC", "Media Status updated:")
}, {
Log.d("ABC", "Error updating Media Status: " + it.message)
isABCControlChangeRequested = false
})
I am not satisfied with this approach, can anyone please direct me right direction to use the rx's full potential. Thanks in advance.
EDIT
Observable.combineLatest(Observable.timer(daleyABCControlResetSeconds, TimeUnit.SECONDS, AndroidSchedulers.mainThread())
.doOnNext {
if (getABCUpdate() != null) {
Log.d("ABC", "Media status reset after 3 seconds: ")
updateABCResponse(getABCUpdate())
}
},
audioApi.setABCUpdate(abcUpdate)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread()),
BiFunction<Long, ABCStatusUpdate, ABCStatusUpdate> { _, abcStatusUpdate ->
abcStatusUpdate
})
.subscribe({
timerDisposable.dispose()
updateABCResponse(abcStatusUpdate)
Log.d("ABC", "Media Status updated:")
}, {
Log.d("ABC", "Error updating Media Status: " + abcStatusUpdate.vol)
isABCControlChangeRequested = false
})
You can use combinelatest, zip or merge for combinig. I think in your case combinelatest is suitable
Observable.combineLatest(
Observable.timer(daleyABCControlResetSeconds, TimeUnit.SECONDS, AndroidSchedulers.mainThread())
.doOnNext {
if (getABCUpdate() != null) {
Log.d("ABC", "Media status reset after 3 seconds: ")
updateABCResponse(getABCUpdate())
}
},
audioApi.setABCUpdate(abcUpdate)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread()),
BiFunction<Long, YourApiResponseType, YourApiResponseType> { _, response ->
response})
.subscribe({
timerDisposable.dispose()
updateABCResponse(it)
Log.d("ABC", "Media Status updated:")
}, {
Log.d("ABC", "Error updating Media Status: " + it.message)
isABCControlChangeRequested = false
})
UPD:
You can change your code like this:
Observable.timer(5, TimeUnit.SECONDS, AndroidSchedulers.mainThread()).startWith(-1L)
.doOnNext {
if (it == -1L) return#doOnNext
//your condition
}
I am using retorift to hit getAricle api and get list of articles related to the user. getArticle api will throw error if token passed is expired if so then I have to call refreshToken api to get new token then again I have to call the getArticle api
ApiController.createRx().getArticle(token)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe({ response -> toast(response.body().url) }, { e ->
println(e.printStackTrace())
if(e is HttpException && e.code() in arrayOf(401,403)){
//Here I want to call refresh tolken api
toast("Auth error")
}
else
toast(R.string.something_went_wrong)
})
Edit
Even though given answers showed some direction but those are not a direct answer to my question. This is how solved it but I feel this can be refactored into much better code
ApiController.createRx().getArticle(Preference.getToken())
.flatMap { value ->
if (value.code() in arrayOf(403, 401)) {
ApiController.refreshToken()
ApiController.createRx().getArticle(Preference.getToken())
} else Observable.just(value)
}
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe({ response -> println("Success") }, { e ->
e.printStackTrace()
toast(R.string.something_went_wrong)
})
fun refreshToken() {
val token:String?=ApiController.createRx().refreshToken(Preferences.getRefreshToken()).blockingFirst()?.body()?.token
if (token != null) Preferences.setAuthToken(token)
}
EDIT
I refactored my code to little more cleaner version
Observable.defer { ApiController.createRx().getArticle(Preferences.getToken()) }
.flatMap {
if (it.code() in arrayOf(401, 403)) {
ApiController.refreshToken()
Observable.error(Throwable())
} else Observable.just(it)
}
.retry(1)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe({println("Success") }, {
it.printStackTrace()
toast(R.string.something_went_wrong)
})
fun refreshToken() {
var token: String? = null
try {
token = createRx().refreshToken(Preferences.getRefreshToken()).blockingFirst().body()!!.token
} catch (e: Exception) {
throw e
}
println("saving token")
if (token != null) Preferences.setAuthToken(token)
}
EDIT
Please check my answer for the final refactored code
I have implemented this exact thing. Here is a slightly modified version of that code:
private Observable<Object> refreshTokenIfNotAuthorized(Observable<? extends Throwable> errors) {
final AtomicBoolean alreadyRetried = new AtomicBoolean(false);
return errors.flatMap(error -> {
boolean isAuthorizationError = /* some logic analyzing each error*/ ;
if (isAuthorizationError && !alreadyRetried.get()) {
try {
alreadyRetried.set(true);
String newToken = federatedTokenRefresher.refreshToken()
.toBlocking()
.first();
setLogin(newToken);
return Observable.just(null);
} catch (Exception e) {
return Observable.error(error);
}
}
return Observable.error(error);
});
}
You can use this method like so:
doSomethingRequiringAuth().retryWhen(this::refreshTokenIfNotAuthorized);
What kind of error you will received?. It´s seems like you could use onErrorResumeNext operator.
This operator once that receive a throwable, allow you to return an Observable instead the throwable in the onError
#Test
public void observableOnErrorResumeException() {
Integer[] numbers = {0, 1, 2, 3, 4, 5};
Observable.from(numbers)
.doOnNext(number -> {
if (number > 3) {
try {
throw new IllegalArgumentException();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
})
.onErrorResumeNext(t -> Observable.just(666))
.subscribe(System.out::println);
}
You can see more examples here https://github.com/politrons/reactive/blob/master/src/test/java/rx/observables/errors/ObservableExceptions.java
I will give you another option using groupBy operator
/**
* In this example we create a response code group.
*/
#Test
public void testGroupByCode() {
Observable.from(Arrays.asList(401,403, 200))
.groupBy(code -> code)
.subscribe(groupByCode -> {
switch (groupByCode.getKey()) {
case 401: {
System.out.println("refresh token");
processResponse(groupByCode);
break;
}
case 403: {
System.out.println("refresh token");
processResponse(groupByCode);
break;
}
default: {
System.out.println("Do the toast");
processResponse(groupByCode);
}
}
});
}
private void processResponse(GroupedObservable<Integer, Integer> groupByCode) {
groupByCode.asObservable().subscribe(value -> System.out.println("Response code:" + value));
}
I solved my problem after reading more about RxJava and this is how I implemented it.
First of all will retrofit throw 4xx error to onError or onNext\onSuccess depends on how we define it.
Ex:
#GET("content")
fun getArticle(#Header("Authorization") token: String):Single<Article>
this will throw all the 4xx errors to onError and instead of Single<Article> if you define it as Single<Response<Article>> then all the response from server including 4xx will go to onNext\onSuccess
Single.defer { ApiController.createRx().getArticle(Preferences.getAuthToken())}
.doOnError {
if (it is HttpException && it.code() == 401)
ApiController.refreshToken()
}
.retry { attempts, error -> attempts < 3 && error is HttpException && error.code() == 401 }
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe({println("Success") }, {
it.printStackTrace()
toast(R.string.something_went_wrong)
})
I am using defer as a wrapper around my actual Observable because I want to recreate the article fetch observable on retry after token refresh because I want Preferences.getAuthToken() to be called again as my refresh token code stores newly fetched token in preference.
retry returns true if the HttpException is 401 and not attempted retry more than 2 times