Apollo-android: How can I get ErrorBody from the server

Created on 21 Dec 2017  路  13Comments  路  Source: apollographql/apollo-android

Hello!
I want to use this component to work with our server graphql,
But Our development server will put a message in the body of the error:
{ "errors": [ { "errorCode": "EMAIL_ALREADY_EXIST" } ] }

how can I get a message
"errorCode": "EMAIL_ALREADY_EXIST"
if the server at this point responds with 400
at the moment when I reply to the server, I get an exception:
400 bad request

thanks for the help

Question

All 13 comments

You can check com.apollographql.apollo.api.Response#errors and com.apollographql.apollo.api.Error#customAttributes particularly.

Or you can override com.apollographql.apollo.ApolloCall.Callback#onHttpError in your callback, but you have to close response manually then e.rawResponse().close()

Could you show a small example, for both options, please

Apollo android are generating class for subscriptions that implement Query interface instead Subscription, in the build process.
`

 final String id_not = "1";
    ApolloSubscriptionCall<TestsAddedSubscription.Data> subscriptionCall = 
    app.getApolloClientWhitTokenWSS(MainActivity.this)
            .subscribe(new TestsAddedSubscription(id_not));

    disposables.add(Rx2Apollo.from(subscriptionCall)
            .subscribeOn(Schedulers.io())
            .observeOn(AndroidSchedulers.mainThread())
            .subscribeWith(
                    new DisposableSubscriber<Response<TestsAddedSubscription.Data>>() {
                        @Override
                        public void onNext(Response<TestsAddedSubscription.Data> response) {
                            showNotification(response.data().notificationAdded().title(),
                                    response.data().notificationAdded().content(), 1);
                        }

                        @Override
                        public void onError(Throwable e) {

                        }

                        @Override
                        public void onComplete() {

                        }
                    }
            )
    );

subscription TestsAddedSubscription($id:ID!){

  notificationAdded(id: $id){
        id
        from
        title
        content
        data
        createdAt
        updatedAt
        notificationTypeId
        notificationType{
          description
        }
        Users{
          userName
        }
      }
    }

public ApolloClient getApolloClientWhitTokenWSS(Context context) {
OkHttpClient token = createOkHttpWithValidToken(context);
apolloClient = ApolloClient.builder()
.serverUrl(BASE_URL)
.okHttpClient(token)
.normalizedCache(normalizedCacheFactory, resolver)
.subscriptionTransportFactory(new WebSocketSubscriptionTransport.Factory(SUBSCRIPTION_BASE_URL, token))
.build();
return apolloClient;
} `

Closing due to inactivity

@sav007 Could you please reopen this? I want to know how this would be done today.

A short example:

apolloClient.rxMutate(myMutation())
                    .subscribeOn(Schedulers.io())
                    .observeOn(AndroidSchedulers.mainThread())
                    .subscribeBy(
                            onError = {
                                error ->
                                if (error is ApolloHttpException) {
                                    val networkResponse = error.rawResponse().body().contentType()
                                    val rawResponseString = error.rawResponse().body().contentLength()
                                }
                            }
                    )

The debugger tells me contentType() is "application/json" and contentLength() -1.
But I have charles as ssl proxy running and I see my expected json response body there for the error. So the content gets lost somehow?

Hi @bin101 馃憢 . The RxJava extensions do not expose the rawResponse. You'll need to either use the callback interface or define a custom rxMutate() for your use case. You can start from the existing implementation: https://github.com/apollographql/apollo-android/blob/bca4de4d05cb9bcc30469648eb2dc13d9ad7d455/apollo-rx3-support/src/main/java/com/apollographql/apollo/rx3/Rx3Apollo.java#L51

And implement the onHttpError method:

  public static <T> Observable<Response<T>> from(@NotNull final ApolloQueryWatcher<T> watcher) {
    checkNotNull(watcher, "watcher == null");
    return Observable.create(new ObservableOnSubscribe<Response<T>>() {
      @Override public void subscribe(final ObservableEmitter<Response<T>> emitter) throws Exception {
        ApolloQueryWatcher<T> clone = watcher.clone();
        cancelOnObservableDisposed(emitter, clone);

        clone.enqueueAndWatch(new ApolloCall.Callback<T>() {

          ....

          @Override public void onHttpError(@NotNull ApolloHttpException e) {
            Exceptions.throwIfFatal(e);
            if (!emitter.isDisposed()) {
              Response response = e.rawResponse();
              // do something with response
              response.close;
              emitter.onError(e);
            }
          }
        });
      }
    });

Hi @martinbonnin thank you for your answer but how would I start to implement my custom rxMutate() Method?
How would the boilerplate code look like? I never wrote a kotlin extension. And the app is still a mixture of java and kotlin.
I need to access the raw response from both languages with Rx2, would it be possible to expose it from within onError?

Sorry to ask for detailed examples, I am still relatively new inside the android world.

Hi @bin101 no worries at all! This stuff is not easy and even more complex when there needs to be Java + Kotlin interop.

You'll need to implement a bunch of boilerplate. Something like:

class ApolloExceptionWithHttpBody(cause: ApolloHttpException, val httpBody: String?): Exception(cause.message)

@CheckReturnValue
fun <D : Operation.Data> ApolloMutationCall<D>.rxWithHttpErrorBody(): Observable<Response<D>> {
  return Observable.create { emitter ->
    val clone = clone()
    emitter.setDisposable(object : Disposable {
      override fun dispose() = clone.cancel()
      override fun isDisposed() = clone.isCanceled
    })

    clone.enqueue(object : ApolloCall.Callback<D>() {
      override fun onResponse(response: Response<D>) {
        if (!emitter.isDisposed) {
          emitter.onNext(response)
        }
      }

      override fun onFailure(e: ApolloException) {
        Exceptions.throwIfFatal(e)
        if (!emitter.isDisposed) {
          emitter.onError(e)
        }
      }

      override fun onHttpError(e: ApolloHttpException) {
        val responseBody = e.rawResponse()?.body()?.string()
        if (!emitter.isDisposed) {
          emitter.onError(ApolloExceptionWithHttpBody(e, responseBody))
        }
      }

      override fun onStatusEvent(event: ApolloCall.StatusEvent) {
        if (event === ApolloCall.StatusEvent.COMPLETED && !emitter.isDisposed) {
          emitter.onComplete()
        }
      }
    })
  }
}

Test:

  @Test
  @Throws(Exception::class)
  fun canGetHttpErrorBody() {
    server.enqueue(MockResponse().apply {
      setResponseCode(500)
      body = Buffer().apply { writeUtf8("Ooops") }
    })
    val updateReviewMutation = UpdateReviewMutation(
            "empireReview2",
            ReviewInput.builder()
                    .favoriteColor(ColorInput.builder().blue(1.0).build())
                    .build()
    )

    val errors = apolloClient.mutate(updateReviewMutation)
            .rxWithHttpErrorBody()
            .test()
            .errors()
    assertThat(errors.size == 1)
    assertThat((errors.first() as? ApolloExceptionWithHttpBody)?.httpBody).isEqualTo("Ooops")
  }

~I don't have the main branch checked out right now so don't take this as gospel but it should give a good idea of what is needed. I'll edit this a bit later today when I get a chance to test.~

Hi @martinbonnin your example didn't quite worked out of the box. So I switched to a solution were I copy the relevant methods from Rx2Apollo.java and RxJavaExtensions.kt with some renaming and implemented the onHttpError inside the java from method overload. That works so far but I think your approach would be much smaller. Maybe you could still finish it give me a better example to learn something?
And by the way is there a reason why the raw response is not exposed by default? My colleague who implements the native iOS app said he had no problem with that and the RxSwift extension (I know the RxSwift extension is not officially supported by the Apollo team).

@bin101 sorry for the delay, I just updated the snippet above.

And by the way is there a reason why the raw response is not exposed by default

A HTTP response could be arbitrary large. Exposing it would delay the callback until the response is read and also use more memory. GraphQL has its own mechanism for error handling so reading the HTTP error body isn't needed in most of the use cases and we can save these resources.

@martinbonnin You can close this again :) Thanks for your help

Thanks for the heads up!

Was this page helpful?
0 / 5 - 0 ratings