Handle response.body == null in RX - android

i use Rx in android below
repository.getUserList()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe({ response ->
val userList = response.body()!!
view.showUser(userList)
}, { throwable ->
handlerException(view, throwable)
}).addToDisposable()
It works.
But sometimes response.body() is null, maybe network error or server not response,
then app will crash.
so i have to add try catch like this
repository.getUserList()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe({ response ->
try {
val userList = response.body()!!
view.showUser(userList)
} catch (e: Exception) {
handleException(view, e)
}
}, { throwable ->
handleException(view, throwable)
}).addToDisposable()
But I think there should be a better way to handler error in rx.
can anyone help me, thanks.

you can use filter operator
.filter { it != null }
this was RX way but you can use kotlin null safety like this:
.subscribe({ response ->
response.body()?.let{
view.showUser(userList)
}
}
hope it helped 👍🏻

Related

How to get the response of another observable when error occurs?

I just want to ask if it is possible to get the response of another observable after encountering an error from the another observable?
for example I am calling a two api Avatar and Attachment using a combineLatest.
val avatar: Observable<ResponseBody> = api().getAvatar()
val attachment: Observable<ResponseBody> = api().getAttachment()
val obs = Observables.combineLatest(avatar, attachment)
.map { it ->
if (it.first is Exception) {
Log.e(TAG, "getAvatar failed")
} else {
updateAvatar()
}
if (it.second is Exception) {
Log.e(TAG, "getAttachment failed")
} else {
updateAttachment()
}
if (it.first !is Exception && it.second !is Exception) {
Log.i(TAG, "success first=${it.first}, second=${it.second}")
updateAll()
}
}
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.onErrorReturn { it }
.subscribe()
disposable.add(obs)
I just want to get the avatar response if the attachment error and I want to get the attachment response if the avatar error.
Thanks.
Yes, my friend. You can handle error for each observable that you combine by calling onErrorReturn() method. You can use empty ResponseBody for detecting error. Final code
val avatar: Observable<Optional<ResponseBody>> = api().getAvatar().onErrorReturn{ Optional.empty }
val attachment: Observable<Optional<ResponseBody>> = api().getAttachment().onErrorReturn{ Optional.empty }
val obs = Observables.combineLatest(avatar, attachment) {avatar, attachment ->
if (!avatar.isPresent()) {
//logic
}
if (!attachment.isPresent()) {
//logic
}
}.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.onErrorReturn { it }
.subscribe()
If you use java 7 or lower in you project, you can write your own Optional
class Optional<T>(val value: T?) {
companion object {
fun <T> empty(): Optional<T> = Optional(null)
}
fun isPresent() = value != null
}

Error while trying to fetch data from Internet without Wifi connection using RxJava and Kotlin

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)

RxJava with Firestore Realtime data

I have repository classes. In these classes I make simple collection("..").get() like this.
override fun getTestCollectionItems(): Observable<TestModel> {
return Observable.create { subscriber ->
firebaseFirestore.collection(TEST_COLLECTION)
.get()
.addOnCompleteListener { task ->
if (task.isSuccessful()) {
for (document in task.getResult()) {
if (document.exists()) {
val documentModel = document.toObject(TestModel::class.java)
subscriber.onNext(documentModel)
}
}
subscriber.onComplete()
} else {
subscriber.onError(task.exception!!)
}
}
}
}
But I found the Real time Firecloud option. If I move the Listener to the Repository then is it good meaning?
I tried the next one:
override fun getRealTimeCollection() : Observable<TestModel> {
return Observable.create { subscriber ->
firebaseFirestore.collection(TEST_COLLECTION).document("3lPtYZEEhPdfvZ1wfHIP")
.addSnapshotListener(EventListener<DocumentSnapshot> { snapshot, e ->
if (e != null) {
Log.w("test", "Listen failed.", e)
subscriber.onError(e)
return#EventListener
}
if (snapshot != null && snapshot.exists()) {
Log.d("test", "Current data: " + snapshot.data)
val documentModel = snapshot.toObject(TestModel::class.java)
subscriber.onNext(documentModel)
} else {
Log.d("test", "Current data: null")
}
})
}
}
with DisposableObservable. But when I disposed it, then the Firebase still sent new datas. It will be memory leak. How can I use the RxJava for this situation?
Is it correct to move Real Time data to Repository?
Thank you!
When you create Observable, using the Observable.create method, you get actually ObservableEmitter<T>, with this emitter you should add Cancellable or Disposable using setCancellable()/setDisposable. (you can read about the difference here)
These callbacks will be triggered when you'll dispose your Observable and there you should add the proper un-registration logic of firestore.
override fun getRealTimeCollection(): Observable<TestModel> {
return Observable.create { emitter ->
val listenerRegistration = firebaseFirestore.collection(TEST_COLLECTION).document("3lPtYZEEhPdfvZ1wfHIP")
.addSnapshotListener(EventListener<DocumentSnapshot> { snapshot, e ->
if (e != null) {
Log.w("test", "Listen failed.", e)
emitter.onError(e)
return#EventListener
}
if (snapshot != null && snapshot.exists()) {
Log.d("test", "Current data: " + snapshot.data)
val documentModel = snapshot.toObject(TestModel::class.java)
emitter.onNext(documentModel)
} else {
Log.d("test", "Current data: null")
}
})
emitter.setCancellable { listenerRegistration.remove() }
}
}
I believe you shouldn't wrap firebase functions that way as it will not respect schedulers you use when you subscribe, the firebase's callbacks are executed on the main thread.
So if you do:
wrappedFirestore.flatMap{ networkCall }
.subscribeOn(ioScheduler)
.subscribe()
It will still fail with NetworkOnMainThreadException.

Getting NetworkOnMainThreadException with RxKotlin

I'm trying to make a network request using RxKotlin, but keep getting a NetworkOnMainThreadException I'm subscribing on the main thread, so I'm not sure why it's not taking it off of the UI thread.
Here is where I subscribe to the Observable
weatherInteractor.getWeather(lat, lng)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
{response ->
try {
val jsonData = response.body().string()
val currentWeather = getCurrentWeatherData(jsonData)
view!!.displayCurrentWeather(currentWeather)
} catch (e: JSONException) {
Log.d("Present JSON Exception", e.message)
} catch (e: IOException) {
Log.d("Present IO Exception", e.message)
}
},
{
error ->
error.printStackTrace()
}
)
}
Here is where I create my Observable
fun getWeather(lat: Double, lng: Double): Observable<Response> {
val URL = ""
val client = OkHttpClient()
val request = Request.Builder()
.url(URL)
.build()
return Observable.create { em ->
try {
val response = client.newCall(request).execute()
em.onNext(response)
em.onComplete()
} catch (err: IOException) {
err.printStackTrace()
em.onError(err)
}
}
}
It seems that you confuse subscribeOn and observeOn methods.
subscribeOn specifies the scheduler observable will be created on and will operate on. (You specify it once, position doesn't matter).
observeOn changes the scheduler for every action you type after it.
You can use it multiple times and each set of actions will be executed on specified scheduler.
Here is an example:
Observable
.just("test")
.subscribeOn(Schedulers.io())
.map(s -> { //this and all Observable code is executed on Schedulers.io()
return s;
})
.observeOn(Schedulers.computation())
.map(s -> { // executed on Schedulers.computation()
return s;
})
.observeOn(AndroidSchedulers.mainThread())
.subscribe(s -> { // executed on Android main thread
}, throwable -> {
});

RxJava: Execute second observables only if first one throws an error and repeat from the first

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

Categories

Resources