1

I'm using RxJava2 with Retrofit and OkHttp in Android application that I'm working on.

For this app I am using Kotlin, not that it makes any difference whether it's Java or Kotlin.

In previous apps that I've worked on, when the server returns a 400 error code, the onError callback is called. However today while working on this setup, the onError callback only fires when for example the server is down but onNext is called even for 400 error codes.

below are extracts from my setup

/**
 * Networking
 */
implementation "com.squareup.okhttp3:okhttp:3.11.0"
implementation "com.squareup.okhttp3:logging-interceptor:3.11.0"
implementation "com.squareup.retrofit2:retrofit:2.4.0"
implementation "com.squareup.retrofit2:adapter-rxjava2:2.4.0"
/**
 * Rx
 */
implementation "io.reactivex.rxjava2:rxandroid:2.0.2"
implementation "io.reactivex.rxjava2:rxkotlin:2.2.0"

This is an extract from a dagger2 module which actually builds the retrofit object

    @Singleton
    @Provides
    fun provideRetrofit(okHttpClient: OkHttpClient,
                        gson: Gson,
                        @BaseUrl baseUrl: String): Retrofit {
        return Retrofit.Builder()
                .addConverterFactory(GsonConverterFactory.create(gson))
                .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
                .baseUrl(baseUrl)
                .client(okHttpClient)
                .build()
    }

This is the actual code that calls the API

fun loginUser(username: String, password: String): MutableLiveData<Result> {
        if (username.isNotBlank() && password.isNotBlank()) {
            subscriptions.addAll(remoteUserRepository
                    .getUser(UserDto(username, password))
                    .subscribeBy(onNext = {
                        saveUser(it.body())
                        saveAuthenticationToken(it.headers().get("token"))
                    }, onError = { handleError(it) }))
        }
        return resultLiveData
    }

    private fun saveUser(user: User?) {
        user?.let {
            localUserRepository.saveUser(it)
            resultLiveData.value = Result.SUCCESS
        }
    }

    private fun saveAuthenticationToken(token: String?) {
        token?.let { localUserRepository.saveToken(it) }
    }

    override fun handleError(error: Throwable) {
        super.handleError(error)
        resultLiveData.value = Result.FAILURE
    }

This is a log from a request and response to the server which clearly shows that the server is returning an http code 401

   D/OkHttp: --> POST http://10.0.2.2:3000/login http/1.1
    Content-Type: application/json; charset=UTF-8
D/OkHttp: Content-Length: 48
    Host: 10.0.2.2:3000
    Connection: Keep-Alive
    Accept-Encoding: gzip
    User-Agent: okhttp/3.11.0
    --> END POST
D/OkHttp: <-- 401 Unauthorized http://10.0.2.2:3000/login (20ms)
    content-type: application/json; charset=utf-8
    cache-control: no-cache
    content-length: 94
    Date: Mon, 13 Aug 2018 13:55:07 GMT
    Connection: keep-alive
    <-- END HTTP

Even though the server is returning a 401 error, the handleError(error: Throwable) callback is never called. It is only called in case the server is not reachable.

What am I missing?

5
  • onError is only called for errors. While 4xx represents an unsuccessful call as far as data goes, it is not erroneous of nature. You can distinguish between successful and unsuccessful calls in the success callback (onNext or something) using Response<T>#isSuccessful() Commented Aug 13, 2018 at 14:27
  • @TimCastelijns how is it done in here: medium.com/mindorks/… plus I remember in RxJava1 onError was also called for 400 status code responses. Do you have any idea if this behavior was changed? Commented Aug 13, 2018 at 14:33
  • as you can see this example you link to only handles errors with the call itself, not "errors" with the transmitted data Commented Aug 13, 2018 at 14:35
  • but he's checking for if (e instanceof HttpException) which happens with transmitted errors, or am i mistaken? Commented Aug 13, 2018 at 14:36
  • it's easiest to just check it for yourself. Attach a debugger, trigger a 401 response and see what happens Commented Aug 13, 2018 at 14:40

2 Answers 2

2

onError does catch errors which occur due to execution of the task or handling the result.

In your case retrofit call is ok.

You have to handle the result of response: In your onSuccess check if response isSuccessful and move on

Sign up to request clarification or add additional context in comments.

Comments

1

It turns out the behavior faced was correct because of the following:

@POST("/login")
fun login(@Body userDto: UserDto): Observable<Response<User>>

as you can see that the return type of the function is Observable<Response<User>> more precisely Response<User>

So whenever a response is received irrespective of the http status code, onNext is called. The only time onError is called when a response can't be received.

So in the case where you want a Response back, you will need to use the method suggested by @MaxAves

If you want onError to be called when your server returns a 400 error code you will need to modify the interface so that it becomes like the following:

@POST("/login")
fun login(@Body userDto: UserDto): Observable<User>

In my case this is not doable because I need access to the response headers.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.