Handle an empty response in a JSONRequest with Volley
Asked Answered
S

4

17

I'm using Volley to make a POST request in my application, and in my case, a good response is a 201 with an empty body. I'm using a JSONRequest, to make the call.

My problem is that the error response handler is getting called because response is empty.

Below is my request:

    Request request = new JsonRequest<Object>(Request.Method.POST, url, body, new Response.Listener<Object>() {

        @Override
        public void onResponse(Object response) {

        }
    }, new ErrorListener(context)) {

        @Override
        protected Response<Object> parseNetworkResponse(NetworkResponse response) {

            Log.d(TAG, "success!!!!!!");
            if (response.statusCode == 201)
                mListener.resetPasswordWasSent();
            return null;
        }

        @Override
        public Map<String, String> getHeaders() throws AuthFailureError {
            Map<String,String> params = new HashMap<String, String>();
            params.put("Content-Type","application/json");
            params.put("Accept", "application/json");
            return params;
        }
    };

    requestQueue.add(request);

My parseNetworkResponse function is getting called, then the ErrorListener, and the onResponse method never gets hits because I get a NullPointerException in the ErrorListener.

I can ignore the NullPointerException in my error listener, but I'd prefer not to. Obviously, I can simply send my callback in the parseNetworkResponse, but I don't want to have any errors popping up.

Anyone know how I should handle this?

Edit: Here is the stacktrace:

05-06 09:44:19.586  27546-27560/com.threepoundhealth.euco E/Volley﹕ [1830] NetworkDispatcher.run: Unhandled exception java.lang.NullPointerException
    java.lang.NullPointerException
    at com.android.volley.NetworkDispatcher.run(NetworkDispatcher.java:126)
Samoyed answered 6/5, 2014 at 13:59 Comment(7)
Where exactly do you get the NullPointerException? Inside the Lib or in your code? please add the StackTrace.Deictic
@Deictic please see the editsSamoyed
I have this issue too, my onError is never calledRamonramona
@Ramonramona my onError is getting called - my onResponse is not getting calledSamoyed
Have you initialized mListener?Radicle
@AtulOHolic yes. My resetPasswordWasSent() is getting calledSamoyed
Can you paste this method - resetPasswordWasSent()Radicle
E
17

You could try to hack like this. Create a JsonObjectRequest subclass, override the parseNetworkResponse method and check the response data, if it is an empty byte[], replace the data with byte[] representation of a empty json {}.

public class VolleyJsonRequest extends JsonObjectRequest {

    ...

    @Override
    protected Response<JSONObject> parseNetworkResponse(NetworkResponse response) {
        try {
            if (response.data.length == 0) {
                byte[] responseData = "{}".getBytes("UTF8");
                response = new NetworkResponse(response.statusCode, responseData, response.headers, response.notModified);
            }
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        return super.parseNetworkResponse(response);
    }    
}
Ekaterinoslav answered 4/7, 2014 at 4:46 Comment(1)
Great answer! That NetworkResponse constructor however, has been deprecated. For convenience, I write the new one: response = new NetworkResponse(response.statusCode, responseData, response.notModified, response.networkTimeMs, response.allHeaders);Maeganmaelstrom
K
8

You can use a StringRequest. Your listener will be called with an empty String "".

Klondike answered 7/5, 2014 at 3:56 Comment(2)
I'd like to be able to send JSON though, and not use the 'getParams' method where I'd have to create a map of the paramsSamoyed
Using JsonRequest and JsonBodyRequest appears to give an error on an empty response. I'd imagine thats a bug but it seems like StringRequest is only way to handle an empty response without triggering the errorListener.Klondike
P
1

If no response is expected, then JsonRequest can be extended with that in mind.

An example in Kotlin:

/**
 * A request containing a [JSONObject] body for a given URL, expecting an empty response.
 */
class JsonObjectRequestEmptyResponse
/**
 * Creates a new request.
 * @param method the HTTP method to use
 * @param url URL to fetch the JSON from
 * @param jsonRequest A [JSONObject] to post with the request.
 * @param listener Listener to receive a successful response
 * @param errorListener Error listener, or null to ignore errors.
 */
(method: Int, url: String, jsonRequest: JSONObject,
 listener: () -> Unit, errorListener: ErrorListener?) : JsonRequest<Unit>(method, url,
        jsonRequest.toString(), Listener<Unit> { _response -> listener() }, errorListener) {

    override fun parseNetworkResponse(response: NetworkResponse): Response<Unit> {
        if (response.data.isEmpty()) {
            return Response.success(Unit,
                    HttpHeaderParser.parseCacheHeaders(response))
        } else {
            return Response.error(VolleyError("unexpected data"))
        }
    }
}
Prescriptive answered 16/2, 2018 at 20:37 Comment(0)
A
0

I got the same problem and developed a global solution to handle it (and few lack of functionality of Volley), I posted it on another thread but I think it can help many people which are looking for a solution to your problem.

  /**
  * Created by laurentmeyer on 25/07/15.
  */
 public class GenericRequest<T> extends JsonRequest<T> {

     private final Gson gson = new Gson();
     private final Class<T> clazz;
     private final Map<String, String> headers;
     // Used for request which do not return anything from the server
     private boolean muteRequest = false;

     /**
      * Basically, this is the constructor which is called by the others.
      * It allows you to send an object of type A to the server and expect a JSON representing a object of type B.
      * The problem with the #JsonObjectRequest is that you expect a JSON at the end.
      * We can do better than that, we can directly receive our POJO.
      * That's what this class does.
      *
      * @param method:        HTTP Method
      * @param classtype:     Classtype to parse the JSON coming from the server
      * @param url:           url to be called
      * @param requestBody:   The body being sent
      * @param listener:      Listener of the request
      * @param errorListener: Error handler of the request
      * @param headers:       Added headers
      */
     private GenericRequest(int method, Class<T> classtype, String url, String requestBody,
                           Response.Listener<T> listener, Response.ErrorListener errorListener, Map<String, String> headers) {
         super(method, url, requestBody, listener,
                 errorListener);
         clazz = classtype;
         this.headers = headers;
         configureRequest();
     }

     /**
      * Method to be called if you want to send some objects to your server via body in JSON of the request (with headers and not muted)
      *
      * @param method:        HTTP Method
      * @param url:           URL to be called
      * @param classtype:     Classtype to parse the JSON returned from the server
      * @param toBeSent:      Object which will be transformed in JSON via Gson and sent to the server
      * @param listener:      Listener of the request
      * @param errorListener: Error handler of the request
      * @param headers:       Added headers
      */
     public GenericRequest(int method, String url, Class<T> classtype, Object toBeSent,
                           Response.Listener<T> listener, Response.ErrorListener errorListener, Map<String, String> headers) {
         this(method, classtype, url, new Gson().toJson(toBeSent), listener,
                 errorListener, headers);
     }

     /**
      * Method to be called if you want to send some objects to your server via body in JSON of the request (without header and not muted)
      *
      * @param method:        HTTP Method
      * @param url:           URL to be called
      * @param classtype:     Classtype to parse the JSON returned from the server
      * @param toBeSent:      Object which will be transformed in JSON via Gson and sent to the server
      * @param listener:      Listener of the request
      * @param errorListener: Error handler of the request
      */
     public GenericRequest(int method, String url, Class<T> classtype, Object toBeSent,
                           Response.Listener<T> listener, Response.ErrorListener errorListener) {
         this(method, classtype, url, new Gson().toJson(toBeSent), listener,
                 errorListener, new HashMap<String, String>());
     }

     /**
      * Method to be called if you want to send something to the server but not with a JSON, just with a defined String (without header and not muted)
      *
      * @param method:        HTTP Method
      * @param url:           URL to be called
      * @param classtype:     Classtype to parse the JSON returned from the server
      * @param requestBody:   String to be sent to the server
      * @param listener:      Listener of the request
      * @param errorListener: Error handler of the request
      */
     public GenericRequest(int method, String url, Class<T> classtype, String requestBody,
                           Response.Listener<T> listener, Response.ErrorListener errorListener) {
         this(method, classtype, url, requestBody, listener,
                 errorListener, new HashMap<String, String>());
     }

     /**
      * Method to be called if you want to GET something from the server and receive the POJO directly after the call (no JSON). (Without header)
      *
      * @param url:           URL to be called
      * @param classtype:     Classtype to parse the JSON returned from the server
      * @param listener:      Listener of the request
      * @param errorListener: Error handler of the request
      */
     public GenericRequest(String url, Class<T> classtype, Response.Listener<T> listener, Response.ErrorListener errorListener) {
         this(Request.Method.GET, url, classtype, "", listener, errorListener);
     }

     /**
      * Method to be called if you want to GET something from the server and receive the POJO directly after the call (no JSON). (With headers)
      *
      * @param url:           URL to be called
      * @param classtype:     Classtype to parse the JSON returned from the server
      * @param listener:      Listener of the request
      * @param errorListener: Error handler of the request
      * @param headers:       Added headers
      */
     public GenericRequest(String url, Class<T> classtype, Response.Listener<T> listener, Response.ErrorListener errorListener, Map<String, String> headers) {
         this(Request.Method.GET, classtype, url, "", listener, errorListener, headers);
     }

     /**
      * Method to be called if you want to send some objects to your server via body in JSON of the request (with headers and muted)
      *
      * @param method:        HTTP Method
      * @param url:           URL to be called
      * @param classtype:     Classtype to parse the JSON returned from the server
      * @param toBeSent:      Object which will be transformed in JSON via Gson and sent to the server
      * @param listener:      Listener of the request
      * @param errorListener: Error handler of the request
      * @param headers:       Added headers
      * @param mute:          Muted (put it to true, to make sense)
      */
     public GenericRequest(int method, String url, Class<T> classtype, Object toBeSent,
                           Response.Listener<T> listener, Response.ErrorListener errorListener, Map<String, String> headers, boolean mute) {
         this(method, classtype, url, new Gson().toJson(toBeSent), listener,
                 errorListener, headers);
         this.muteRequest = mute;
     }

     /**
      * Method to be called if you want to send some objects to your server via body in JSON of the request (without header and muted)
      *
      * @param method:        HTTP Method
      * @param url:           URL to be called
      * @param classtype:     Classtype to parse the JSON returned from the server
      * @param toBeSent:      Object which will be transformed in JSON via Gson and sent to the server
      * @param listener:      Listener of the request
      * @param errorListener: Error handler of the request
      * @param mute:          Muted (put it to true, to make sense)
      */
     public GenericRequest(int method, String url, Class<T> classtype, Object toBeSent,
                           Response.Listener<T> listener, Response.ErrorListener errorListener, boolean mute) {
         this(method, classtype, url, new Gson().toJson(toBeSent), listener,
                 errorListener, new HashMap<String, String>());
         this.muteRequest = mute;

     }

     /**
      * Method to be called if you want to send something to the server but not with a JSON, just with a defined String (without header and not muted)
      *
      * @param method:        HTTP Method
      * @param url:           URL to be called
      * @param classtype:     Classtype to parse the JSON returned from the server
      * @param requestBody:   String to be sent to the server
      * @param listener:      Listener of the request
      * @param errorListener: Error handler of the request
      * @param mute:          Muted (put it to true, to make sense)
      */
     public GenericRequest(int method, String url, Class<T> classtype, String requestBody,
                           Response.Listener<T> listener, Response.ErrorListener errorListener, boolean mute) {
         this(method, classtype, url, requestBody, listener,
                 errorListener, new HashMap<String, String>());
         this.muteRequest = mute;

     }


     @Override
     protected Response<T> parseNetworkResponse(NetworkResponse response) {
         // The magic of the mute request happens here
         if (muteRequest) {
             if (response.statusCode >= 200 && response.statusCode <= 299) {
                 // If the status is correct, we return a success but with a null object, because the server didn't return anything
                 return Response.success(null, HttpHeaderParser.parseCacheHeaders(response));
             }
         } else {
             try {
                 // If it's not muted; we just need to create our POJO from the returned JSON and handle correctly the errors
                 String json = new String(response.data, HttpHeaderParser.parseCharset(response.headers));
                 T parsedObject = gson.fromJson(json, clazz);
                 return Response.success(parsedObject, HttpHeaderParser.parseCacheHeaders(response));
             } catch (UnsupportedEncodingException e) {
                 return Response.error(new ParseError(e));
             } catch (JsonSyntaxException e) {
                 return Response.error(new ParseError(e));
             }
         }
         return null;
     }

     @Override
     public Map<String, String> getHeaders() throws AuthFailureError {
         return headers != null ? headers : super.getHeaders();
     }

     private void configureRequest() {
         // Set retry policy
         // Add headers, for auth for example
         // ...
     }
 }

Here is the original answer on the other thread.

Abstraction answered 27/7, 2015 at 19:16 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.