zeeshan
zeeshan

Reputation: 45

Uploading Image as Multipart request using Volley?

I'm trying to post my image using Volley but i'm unable to upload my image to the server. I always get com.volley.ServerError. When I capture the image upload request using Fiddler, it gives me a 500 status/error code.

Here is my code for selecting image from gallery:

@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
    super.onActivityResult(requestCode, resultCode, data);

    if (data == null) {
        Toast.makeText(getActivity(), "No Image Selected", Toast.LENGTH_SHORT).show();
    } else if (requestCode == Constants.CHOICE_AVATAR_FROM_GALLERY && resultCode == getActivity().RESULT_OK) {
        Bitmap avatar = getBitmapFromData(data);


        RestClient restClient = new RestClient(getActivity());
  restClient.stringRequest(Constants.PROFILE_IMAGE_UPDATE_REQUEST_ID, Constants.PROFILE_UPDATE_IMAGE_URL, this, imageData);



        byte[] inputData=null;
        try {
            Bundle extra = data.getExtras();
            Uri _uri= Uri.parse(extra.get("src_uri").toString());
            InputStream iStream = getActivity().getContentResolver().openInputStream(_uri);
            inputData = getBytes(iStream);
            size = (inputData.length)/(1024*1024);

            Log.d("ImageArray", "uri:   " + size);
        } catch (Exception e) {
            e.printStackTrace();
        }

        if(size < 3){

            imageView.setImageBitmap(avatar);
            Map<String, String> dataMap = new HashMap<>();
            dataMap.put("Email", Utility.sessionEmail(getActivity()));

            restClient.multiPartImageUploadRequest(Constants.PROFILE_IMAGE_UPDATE_REQUEST_ID,
                    url, this, dataMap, inputData);

        }else {
            imageView.setImageResource(R.drawable.ic_camera);
            Toast.makeText(getActivity(), "Image size shuld not exceed 3 MB", Toast.LENGTH_SHORT).show();
        }

    }


}

public String getPath(Uri uri) {
    Cursor cursor = getActivity().getContentResolver().query(uri, null, null, null, null);
    cursor.moveToFirst();
    String document_id = cursor.getString(0);
    document_id = document_id.substring(document_id.lastIndexOf(":") + 1);
    cursor.close();



    cursor = getActivity().getContentResolver().query(
            android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
            null, MediaStore.Images.Media._ID + " = ? ", new String[]{document_id}, null);
    cursor.moveToFirst();
    String path = cursor.getString(cursor.getColumnIndex(MediaStore.Images.Media.DATA));
    cursor.close();

    return path;
}

public void selectImage(View view) {

    Intent intent = new Intent(Intent.ACTION_GET_CONTENT);

    intent.setType("image/*");
    intent.setAction(Intent.ACTION_GET_CONTENT);
    startActivityForResult(getCropIntent(intent), Constants.CHOICE_AVATAR_FROM_GALLERY);
}


private Intent getCropIntent(Intent intent) {

    intent.putExtra("crop", "true");
    intent.putExtra("aspectX", 1);
    intent.putExtra("aspectY", 1);
    intent.putExtra("outputX", 320);
    intent.putExtra("outputY", 320);
    intent.putExtra("return-data", true);

    return intent;
}

public Bitmap getBitmapFromData(Intent data) {
    Bitmap photo = null;
    Uri photoUri = data.getData();


    if (photoUri != null) {
        photo = BitmapFactory.decodeFile(photoUri.getPath());
        path = getPath(photoUri);
        uri = photoUri;
        Log.d("URI", "" + photoUri);
    }
    if (photo == null) {
        Bundle extra = data.getExtras();
        if (extra != null) {
            photo = (Bitmap) extra.get("data");
            ByteArrayOutputStream stream = new ByteArrayOutputStream();
            photo.compress(Bitmap.CompressFormat.JPEG, 90, stream);

        }
    }
    return photo;
}

This is a method to handle upload POST request

 public void multiPartImageUploadRequest(int id, String url, ResponseHandler handler, Map<String, String> data, byte[] image){

    int request_id = id;
    final ResponseHandler responseHandler = handler;
    final Map<String, String> params = data;
    final byte[] imageData = image;

    pDialog = new ProgressDialog(context);
    pDialog.setMessage("Uploading Image...");
    pDialog.show();

    VolleyMultipartRequest request = new VolleyMultipartRequest(Request.Method.POST, url,
            new Response.Listener<NetworkResponse>() {
        @Override
        public void onResponse(NetworkResponse response) {
            String resultResponse = new String(response.data);
            responseHandler.success(resultResponse, Constants.PROFILE_IMAGE_UPDATE_REQUEST_ID);
            pDialog.dismiss();

        }
    }, new Response.ErrorListener() {
        @Override
        public void onErrorResponse(VolleyError error) {
            responseHandler.failure(error, Constants.PROFILE_IMAGE_UPDATE_REQUEST_ID);
            pDialog.dismiss();
            error.printStackTrace();
        }
    }) {
        @Override
        protected Map<String, String> getParams() {
            return params;
        }

        @Override
        protected Map<String, DataPart> getByteData() {
            Map<String, DataPart> ImageParams = new HashMap<>();
            // file name could found file base or direct access from real path
            // for now just get bitmap data from ImageView
            ImageParams.put("", new DataPart("file_avatar.jpg", imageData, "image/jpeg"));

            return ImageParams;
        }
    };
    request.setRetryPolicy(new DefaultRetryPolicy(30*1000, 1, 1.0f));

    RestController.getInstance().addToRequestQueue(request);



}

VolleyMultipartRequest.java

public class VolleyMultipartRequest extends Request<NetworkResponse> {


    private final String twoHyphens = "--";
    private final String lineEnd = "\r\n";
    private final String boundary = "apiclient-" + System.currentTimeMillis();

    private Response.Listener<NetworkResponse> mListener;
    private Response.ErrorListener mErrorListener;
    private Map<String, String> mHeaders;


    /**
     * Default constructor with predefined header and post method.
     *
     * @param url           request destination
     * @param headers       predefined custom header
     * @param listener      on success achieved 200 code from request
     * @param errorListener on error http or library timeout
     */
    public VolleyMultipartRequest(String url, Map<String, String> headers,
                                  Response.Listener<NetworkResponse> listener,
                                  Response.ErrorListener errorListener) {
        super(Method.POST, url, errorListener);
        this.mListener = listener;
        this.mErrorListener = errorListener;
        this.mHeaders = headers;
    }

    /**
     * Constructor with option method and default header configuration.
     *
     * @param method        method for now accept POST and GET only
     * @param url           request destination
     * @param listener      on success event handler
     * @param errorListener on error event handler
     */
    public VolleyMultipartRequest(int method, String url,
                                  Response.Listener<NetworkResponse> listener,
                                  Response.ErrorListener errorListener) {
        super(method, url, errorListener);
        this.mListener = listener;
        this.mErrorListener = errorListener;
    }

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

    @Override
    public String getBodyContentType() {
        return "multipart/form-data;boundary=" + boundary;
    }

    @Override
    public byte[] getBody() throws AuthFailureError {
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        DataOutputStream dos = new DataOutputStream(bos);

        try {
            // populate text payload
            Map<String, String> params = getParams();
            if (params != null && params.size() > 0) {
                textParse(dos, params, getParamsEncoding());
            }

            // populate data byte payload
            Map<String, DataPart> data = getByteData();
            if (data != null && data.size() > 0) {
                dataParse(dos, data);
            }

            // close multipart form data after text and file data
            dos.writeBytes(twoHyphens + boundary + twoHyphens + lineEnd);

            return bos.toByteArray();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * Custom method handle data payload.
     *
     * @return Map data part label with data byte
     * @throws AuthFailureError
     */
    protected Map<String, DataPart> getByteData() throws AuthFailureError {
        return null;
    }

    @Override
    protected Response<NetworkResponse> parseNetworkResponse(NetworkResponse response) {
        try {
            return Response.success(
                    response,
                    HttpHeaderParser.parseCacheHeaders(response));
        } catch (Exception e) {
            return Response.error(new ParseError(e));
        }
    }

    @Override
    protected void deliverResponse(NetworkResponse response) {
        mListener.onResponse(response);
    }

    @Override
    public void deliverError(VolleyError error) {
        mErrorListener.onErrorResponse(error);
    }

    /**
     * Parse string map into data output stream by key and value.
     *
     * @param dataOutputStream data output stream handle string parsing
     * @param params           string inputs collection
     * @param encoding         encode the inputs, default UTF-8
     * @throws IOException
     */
    private void textParse(DataOutputStream dataOutputStream, Map<String, String> params, String encoding) throws IOException {
        try {
            for (Map.Entry<String, String> entry : params.entrySet()) {
                buildTextPart(dataOutputStream, entry.getKey(), entry.getValue());
            }
        } catch (UnsupportedEncodingException uee) {
            throw new RuntimeException("Encoding not supported: " + encoding, uee);
        }
    }

    /**
     * Parse data into data output stream.
     *
     * @param dataOutputStream data output stream handle file attachment
     * @param data             loop through data
     * @throws IOException
     */
    private void dataParse(DataOutputStream dataOutputStream, Map<String, DataPart> data) throws IOException {
        for (Map.Entry<String, DataPart> entry : data.entrySet()) {
            buildDataPart(dataOutputStream, entry.getValue(), entry.getKey());
        }
    }

    /**
     * Write string data into header and data output stream.
     *
     * @param dataOutputStream data output stream handle string parsing
     * @param parameterName    name of input
     * @param parameterValue   value of input
     * @throws IOException
     */
    private void buildTextPart(DataOutputStream dataOutputStream, String parameterName, String parameterValue) throws IOException {
        dataOutputStream.writeBytes(twoHyphens + boundary + lineEnd);
        dataOutputStream.writeBytes("Content-Disposition: form-data; name=\"" + parameterName + "\"" + lineEnd);
        //dataOutputStream.writeBytes("Content-Type: text/plain; charset=UTF-8" + lineEnd);
        dataOutputStream.writeBytes(lineEnd);
        dataOutputStream.writeBytes(parameterValue + lineEnd);
    }

    /**
     * Write data file into header and data output stream.
     *
     * @param dataOutputStream data output stream handle data parsing
     * @param dataFile         data byte as DataPart from collection
     * @param inputName        name of data input
     * @throws IOException
     */
    private void buildDataPart(DataOutputStream dataOutputStream, DataPart dataFile, String inputName) throws IOException {
        dataOutputStream.writeBytes(twoHyphens + boundary + lineEnd);
        dataOutputStream.writeBytes("Content-Disposition: form-data; name=\"" +
                inputName + "\"; filename=\"" + dataFile.getFileName() + "\"" + lineEnd);
        if (dataFile.getType() != null && !dataFile.getType().trim().isEmpty()) {
            dataOutputStream.writeBytes("Content-Type: " + dataFile.getType() + lineEnd);
            Log.d("Name", inputName + "      " + dataFile.getFileName());
        }
        dataOutputStream.writeBytes(lineEnd);

        ByteArrayInputStream fileInputStream = new ByteArrayInputStream(dataFile.getContent());
        int bytesAvailable = fileInputStream.available();

        int maxBufferSize = 1024 * 1024;
        int bufferSize = Math.min(bytesAvailable, maxBufferSize);
        byte[] buffer = new byte[bufferSize];

        int bytesRead = fileInputStream.read(buffer, 0, bufferSize);

        while (bytesRead > 0) {
            dataOutputStream.write(buffer, 0, bufferSize);
            bytesAvailable = fileInputStream.available();
            bufferSize = Math.min(bytesAvailable, maxBufferSize);
            bytesRead = fileInputStream.read(buffer, 0, bufferSize);
        }

        dataOutputStream.writeBytes(lineEnd);
    }

    /**
     * Simple data container use for passing byte file
     */
    public class DataPart {
        private String fileName;
        private byte[] content;
        private String type;

        /**
         * Default data part
         */
        public DataPart() {
        }

        /**
         * Constructor with data.
         *
         * @param name label of data
         * @param data byte data
         */
        public DataPart(String name, byte[] data) {
            fileName = name;
            content = data;
        }

        /**
         * Constructor with mime data type.
         *
         * @param name     label of data
         * @param data     byte data
         * @param mimeType mime data like "image/jpeg"
         */
        public DataPart(String name, byte[] data, String mimeType) {
            fileName = name;
            content = data;
            type = mimeType;
        }

        /**
         * Getter file name.
         *
         * @return file name
         */
        public String getFileName() {
            return fileName;
        }

        /**
         * Setter file name.
         *
         * @param fileName string file name
         */
        public void setFileName(String fileName) {
            this.fileName = fileName;
        }

        /**
         * Getter content.
         *
         * @return byte file data
         */
        public byte[] getContent() {
            return content;
        }

        /**
         * Setter content.
         *
         * @param content byte file data
         */
        public void setContent(byte[] content) {
            this.content = content;
        }

        /**
         * Getter mime type.
         *
         * @return mime type
         */
        public String getType() {
            return type;
        }

        /**
         * Setter mime type.
         *
         * @param type mime type
         */
        public void setType(String type) {
            this.type = type;
        }
    }
}

Upvotes: 3

Views: 5087

Answers (1)

TommySM
TommySM

Reputation: 3883

So, understanding what exactly is wrong would require some debugging, what I can share is from my experience.

disclaimer: I myself have moved on to using Retrofit2.0, and would recommend anyone to do the same, as it is quicker, easier to use, and significantly more documented (and documented well for that matter), this is from my prior experience with Volley

In my case I needed to upload an image together with a text field, the MultiPart class I built was a little different:

The main thing is I used MultiPartEntityBuilder which simplifies stuff, and the class itself extends Request<String> since that was the response type (which is easier to handle usually), so this would be the constructor:

public MultiPartImageRequest(String url, String filePath, Response.Listener<String> listener, Response.ErrorListener errorListener)
{
    super(Method.POST, url, errorListener);
    this.listener = listener;
    this.entityBuilder = MultipartEntityBuilder.create();
    this.httpEntity = new MultipartEntity();
    setShouldCache(false);
    this.file = new File(filePath);

    this.entityBuilder.setMode(HttpMultipartMode.BROWSER_COMPATIBLE);
    this.entityBuilder.setBoundary(generateBoundary());

    buildMultipartImageEntity();
}

Created the boundary myself, and built the entity with a binary body containing the file, and my text field, giving the the appropriate content type, like so:

private void buildMultipartImageEntity()
{
    try
    {
        ContentType contentType = ContentType.create("image/jpeg");

        entityBuilder.addBinaryBody("userfile", file, contentType, file.getName());
        entityBuilder.addTextBody("userid", String.valueOf(SettingsManager.getUserID()), ContentType.TEXT_PLAIN);
        httpEntity = entityBuilder.build();

    }
    catch (Exception e)
    {
        VolleyLog.e("UnsupportedEncodingException");
    }
}

private static final char[] MULTIPART_CHARS = "-_1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ".toCharArray();

private String generateBoundary()
{
    StringBuilder buffer = new StringBuilder();
    Random rand = new Random();
    int count = rand.nextInt(11) + 30;

    for (int i = 0; i < count; ++i)
    {
        buffer.append(MULTIPART_CHARS[rand.nextInt(MULTIPART_CHARS.length)]);
    }

    return buffer.toString();
}

from there, the getBodyContentType and getBody use the httpEntity object:

@Override
public String getBodyContentType()
{
    return httpEntity.getContentType().getValue();
}

@Override
public byte[] getBody() throws AuthFailureError
{
    ByteArrayOutputStream bos = new ByteArrayOutputStream();
    try
    {
        httpEntity.writeTo(bos);
    }
    catch (IOException e)
    {
        VolleyLog.e("IOException writing to ByteArrayOutputStream");
    }
    return bos.toByteArray();
}

And another thing I found was the need to parse the response in utf-8, but that might not be needed in your case (depending on the response):

@Override
protected Response<String> parseNetworkResponse(NetworkResponse response)
{
    try
    {
        String responseBody = new String(response.data, "utf-8");
        return (Response.success(responseBody, getCacheEntry()));
    }
    catch (UnsupportedEncodingException e)
    {
         VolleyLog.e("UnsupportedEncodingException");
        return (Response.success("Uploaded, problem with url return", getCacheEntry()));
    }
}

Hope this Helps in any way.

Upvotes: 1

Related Questions