114

This is not really a question, however, I would like to share some of my working code here for your reference when you need.

As we know that HttpEntity is deprecated from API22 and comletely removed since API23. At the moment, we cannot access HttpEntity Reference on Android Developer anymore (404). So, the following is my working sample code for POST Multipart Request with Volley and without HttpEntity. It's working, tested with Asp.Net Web API. Of course, the code perhaps is just a basic sample that posts two existed drawable files, also is not the best solution for all cases, and not good tuning.

MultipartActivity.java:

package com.example.multipartvolley; import android.app.Activity; import android.content.Context; import android.graphics.Bitmap; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; import android.os.Bundle; import android.support.v4.content.ContextCompat; import android.view.Menu; import android.view.MenuItem; import android.widget.Toast; import com.android.volley.NetworkResponse; import com.android.volley.Response; import com.android.volley.VolleyError; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.DataOutputStream; import java.io.IOException; public class MultipartActivity extends Activity { private final Context context = this; private final String twoHyphens = "--"; private final String lineEnd = "\r\n"; private final String boundary = "apiclient-" + System.currentTimeMillis(); private final String mimeType = "multipart/form-data;boundary=" + boundary; private byte[] multipartBody; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_multipart); byte[] fileData1 = getFileDataFromDrawable(context, R.drawable.ic_action_android); byte[] fileData2 = getFileDataFromDrawable(context, R.drawable.ic_action_book); ByteArrayOutputStream bos = new ByteArrayOutputStream(); DataOutputStream dos = new DataOutputStream(bos); try { // the first file buildPart(dos, fileData1, "ic_action_android.png"); // the second file buildPart(dos, fileData2, "ic_action_book.png"); // send multipart form data necesssary after file data dos.writeBytes(twoHyphens + boundary + twoHyphens + lineEnd); // pass to multipart body multipartBody = bos.toByteArray(); } catch (IOException e) { e.printStackTrace(); } String url = "http://192.168.1.100/api/postfile"; MultipartRequest multipartRequest = new MultipartRequest(url, null, mimeType, multipartBody, new Response.Listener<NetworkResponse>() { @Override public void onResponse(NetworkResponse response) { Toast.makeText(context, "Upload successfully!", Toast.LENGTH_SHORT).show(); } }, new Response.ErrorListener() { @Override public void onErrorResponse(VolleyError error) { Toast.makeText(context, "Upload failed!\r\n" + error.toString(), Toast.LENGTH_SHORT).show(); } }); VolleySingleton.getInstance(context).addToRequestQueue(multipartRequest); } @Override public boolean onCreateOptionsMenu(Menu menu) { // Inflate the menu; this adds items to the action bar if it is present. getMenuInflater().inflate(R.menu.menu_multipart, menu); return true; } @Override public boolean onOptionsItemSelected(MenuItem item) { // Handle action bar item clicks here. The action bar will // automatically handle clicks on the Home/Up button, so long // as you specify a parent activity in AndroidManifest.xml. int id = item.getItemId(); //noinspection SimplifiableIfStatement if (id == R.id.action_settings) { return true; } return super.onOptionsItemSelected(item); } private void buildPart(DataOutputStream dataOutputStream, byte[] fileData, String fileName) throws IOException { dataOutputStream.writeBytes(twoHyphens + boundary + lineEnd); dataOutputStream.writeBytes("Content-Disposition: form-data; name=\"uploaded_file\"; filename=\"" + fileName + "\"" + lineEnd); dataOutputStream.writeBytes(lineEnd); ByteArrayInputStream fileInputStream = new ByteArrayInputStream(fileData); int bytesAvailable = fileInputStream.available(); int maxBufferSize = 1024 * 1024; int bufferSize = Math.min(bytesAvailable, maxBufferSize); byte[] buffer = new byte[bufferSize]; // read file and write it into form... 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); } private byte[] getFileDataFromDrawable(Context context, int id) { Drawable drawable = ContextCompat.getDrawable(context, id); Bitmap bitmap = ((BitmapDrawable) drawable).getBitmap(); ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); bitmap.compress(Bitmap.CompressFormat.PNG, 0, byteArrayOutputStream); return byteArrayOutputStream.toByteArray(); } } 

MultipartRequest.java:

package com.example.multipartvolley; import com.android.volley.AuthFailureError; import com.android.volley.NetworkResponse; import com.android.volley.ParseError; import com.android.volley.Request; import com.android.volley.Response; import com.android.volley.VolleyError; import com.android.volley.toolbox.HttpHeaderParser; import java.util.Map; class MultipartRequest extends Request<NetworkResponse> { private final Response.Listener<NetworkResponse> mListener; private final Response.ErrorListener mErrorListener; private final Map<String, String> mHeaders; private final String mMimeType; private final byte[] mMultipartBody; public MultipartRequest(String url, Map<String, String> headers, String mimeType, byte[] multipartBody, Response.Listener<NetworkResponse> listener, Response.ErrorListener errorListener) { super(Method.POST, url, errorListener); this.mListener = listener; this.mErrorListener = errorListener; this.mHeaders = headers; this.mMimeType = mimeType; this.mMultipartBody = multipartBody; } @Override public Map<String, String> getHeaders() throws AuthFailureError { return (mHeaders != null) ? mHeaders : super.getHeaders(); } @Override public String getBodyContentType() { return mMimeType; } @Override public byte[] getBody() throws AuthFailureError { return mMultipartBody; } @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); } } 

UPDATE:

For text part, please refer to @Oscar's answer below.

40
  • 1
    I have just copied @Kevin comment at the following question: Some servers are VERY picky. If you have issues add a SPACE between ";" and "filename=" when building Content-Disposition and "multipart/form-data; boundary=" + boundary; :) Commented Sep 28, 2015 at 1:49
  • 1
    if you want to add mimtype: dataOutputStream.writeBytes("Content-Type: image/jpeg" + lineEnd); Commented Sep 28, 2015 at 2:25
  • 1
    @MaorHadad: thanks for your comment :) Commented Sep 28, 2015 at 2:34
  • 1
    Thank you for this great solution. after updating to appcompat 23 this issue was pain in the a** Commented Sep 28, 2015 at 16:54
  • 1
    Dear BNK does this work for uploading video? Commented Nov 11, 2015 at 13:16

6 Answers 6

68

I rewrite your code @RacZo and @BNK more modular and easy to use like

VolleyMultipartRequest multipartRequest = new VolleyMultipartRequest(Request.Method.POST, url, new Response.Listener<NetworkResponse>() { @Override public void onResponse(NetworkResponse response) { String resultResponse = new String(response.data); // parse success output } }, new Response.ErrorListener() { @Override public void onErrorResponse(VolleyError error) { error.printStackTrace(); } }) { @Override protected Map<String, String> getParams() { Map<String, String> params = new HashMap<>(); params.put("api_token", "gh659gjhvdyudo973823tt9gvjf7i6ric75r76"); params.put("name", "Angga"); params.put("location", "Indonesia"); params.put("about", "UI/UX Designer"); params.put("contact", "[email protected]"); return params; } @Override protected Map<String, DataPart> getByteData() { Map<String, DataPart> params = new HashMap<>(); // file name could found file base or direct access from real path // for now just get bitmap data from ImageView params.put("avatar", new DataPart("file_avatar.jpg", AppHelper.getFileDataFromDrawable(getBaseContext(), mAvatarImage.getDrawable()), "image/jpeg")); params.put("cover", new DataPart("file_cover.jpg", AppHelper.getFileDataFromDrawable(getBaseContext(), mCoverImage.getDrawable()), "image/jpeg")); return params; } }; VolleySingleton.getInstance(getBaseContext()).addToRequestQueue(multipartRequest); 

Check full of code VolleyMultipartRequest at my gist.

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

5 Comments

I don't want to convert into byte or string. for my case, server-side expecting a file and multiple texts (key-value pair, multipart form data), not the byte or string. is that possible?
yes it is, you can treat your data in serverside like multipart form data in web form,, actually the code is modify http header request to fit and similar web form so it is the solution you are looking for...
@AhamadullahSaikat you got anything because same thing in my project send multiple request with same name
@AnggaAriWijaya when I upload image its showing invalid image when download it it from server. Please help I stuck badly
@Sagar do you check uploaded file on your server? it's 0KB or missing?
20

Just want to add to the answer. I was trying to figure how to append text fields to the body and created the following function to do it:

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); } 

It is working pretty well.

4 Comments

I have embedded @BNK solution into my app. I am selecting a photo in my phone library and send it via multipart form data, but it takes a couple of seconds (~10-15) before it sends to the server. Is there a way to reduce this overhead? or any other recommendation?
After the deprecation of HttpEntity, multi-part uploads with volley became very cumbersome, plus implementing PATCH requests is a headache so I ended up moving away from volley and implemented RetroFit (square.github.io/retrofit) in all my apps. I would recommend you to do the same because RetroFit gives you better backward compatibility and future proofs your App.
I agree with @RacZo, however I prefer OkHttp :), I still use Volley for other network requests. I have customized Google's volley removing Apache lib, posted to github.com/ngocchung/volleynoapache, however only tested for Get, Post and Multipart.
I need to use PATCH request using Volley library. How can i acheive this.
10

For those who are struggling to send utf-8 parameters and still no luck, the problem I had was in the dataOutputStream, and change the code of @RacZo to below code:

private void buildTextPart(DataOutputStream dataOutputStream, String parameterName, String parameterValue) throws IOException { dataOutputStream.writeBytes(twoHyphens + boundary + lineEnd); dataOutputStream.writeBytes("Content-Disposition: form-data; name=\""); dataOutputStream.write(parameterName.getBytes("UTF-8")); dataOutputStream.writeBytes(lineEnd); dataOutputStream.writeBytes("Content-Type: text/plain; charset=UTF-8" + lineEnd); dataOutputStream.writeBytes(lineEnd); dataOutputStream.write(parameterValue.getBytes("UTF-8")); dataOutputStream.writeBytes(lineEnd); } 

Comments

1

I found a wrapper of the original volley library which is easier to integrate for multi-part requests. It also supports uploading the multi-part data along with other request parameters. Hence I am sharing my code for the future developers who might run into the problem that I was having (i.e. uploading multi-part data using volley along with some other parameters).

Add the following library in the build.gradle file.

dependencies { compile 'dev.dworks.libs:volleyplus:+' } 

Please note that, I removed the original volley library from my build.gradle and used the above library instead which can handle both multi-part and normal requests having similar integration technique.

Then I just had to write the following class which handles the POST request operation.

public class POSTMediasTask { public void uploadMedia(final Context context, String filePath) { String url = getUrlForPOSTMedia(); // This is a dummy function which returns the POST url for you SimpleMultiPartRequest multiPartRequestWithParams = new SimpleMultiPartRequest(Request.Method.POST, url, new Response.Listener<String>() { @Override public void onResponse(String response) { Log.d("Response", response); // TODO: Do something on success } }, new Response.ErrorListener() { @Override public void onErrorResponse(VolleyError error) { // TODO: Handle your error here } }); // Add the file here multiPartRequestWithParams.addFile("file", filePath); // Add the params here multiPartRequestWithParams.addStringParam("param1", "SomeParamValue1"); multiPartRequestWithParams.addStringParam("param2", "SomeParamValue2"); RequestQueue queue = Volley.newRequestQueue(context); queue.add(multiPartRequestWithParams); } } 

Now execute the task like the following.

new POSTMediasTask().uploadMedia(context, mediaPath); 

You can upload one file at a time using this library. However, I could manage to upload multiple files, just by initiating multiple tasks.

Hope that helps!

Comments

1

Here is a Kotlin version of a class allowing multipart request with Volley 1.1.1.

It's mostly based on @BNK's solution but slighly simplified. I did not notice any particular performance issue. I uploaded a 5Mb pic in about 3 seconds.

class MultipartWebservice(context: Context) { private var queue: RequestQueue? = null private val boundary = "apiclient-" + System.currentTimeMillis() private val mimeType = "multipart/form-data;boundary=$boundary" init { queue = Volley.newRequestQueue(context) } fun sendMultipartRequest( method: Int, url: String, fileData: ByteArray, fileName: String, listener: Response.Listener<NetworkResponse>, errorListener: Response.ErrorListener ) { // Create multi part byte array val bos = ByteArrayOutputStream() val dos = DataOutputStream(bos) buildMultipartContent(dos, fileData, fileName) val multipartBody = bos.toByteArray() // Request header, if needed val headers = HashMap<String, String>() headers["API-TOKEN"] = "458e126682d577c97d225bbd73a75b5989f65e977b6d8d4b2267537019ad9d20" val request = MultipartRequest( method, url, errorListener, listener, headers, mimeType, multipartBody ) queue?.add(request) } @Throws(IOException::class) private fun buildMultipartContent(dos: DataOutputStream, fileData: ByteArray, fileName: String) { val twoHyphens = "--" val lineEnd = "\r\n" dos.writeBytes(twoHyphens + boundary + lineEnd) dos.writeBytes("Content-Disposition: form-data; name=\"file\"; filename=\"$fileName\"$lineEnd") dos.writeBytes(lineEnd) dos.write(fileData) dos.writeBytes(lineEnd) dos.writeBytes(twoHyphens + boundary + twoHyphens + lineEnd) } class MultipartRequest( method: Int, url: String, errorListener: Response.ErrorListener?, private var listener: Response.Listener<NetworkResponse>, private var headers: MutableMap<String, String>, private var mimeType: String, private var multipartBody: ByteArray ) : Request<NetworkResponse>(method, url, errorListener) { override fun getHeaders(): MutableMap<String, String> { return if (headers.isEmpty()) super.getHeaders() else headers } override fun getBodyContentType(): String { return mimeType } override fun getBody(): ByteArray { return multipartBody } override fun parseNetworkResponse(response: NetworkResponse?): Response<NetworkResponse> { return try { Response.success(response, HttpHeaderParser.parseCacheHeaders(response)) } catch (e: Exception) { Response.error(ParseError(e)) } } override fun deliverResponse(response: NetworkResponse?) { listener.onResponse(response) } } } 

3 Comments

Hi. Any more information as to how we can use the above class? For example how can we upload a .jpg image with it ?
Just making sure I understand how this works. Does this create a temporary file called fileName, or does it upload the file called fileName?
Still unable to get this to work. As far as I can tell, this sends the entire contents in 1 piece--not multiple pieces as I expected. My server cannot accept large pieces, that's why I'm trying to use multi-part POST requests. Could you please provide more info or an example?
1

Here is very simplified Kotlin Version for Multipart Volley POST Request

 private fun saveProfileAccount(concern: String, selectedDate: String, WDH: String, details: String, fileExtention: String) { val multipartRequest: VolleyMultipartRequest = object : VolleyMultipartRequest( Request.Method.POST, APIURLS.WhistleBlower, Response.Listener<JSONObject> { response -> }, Response.ErrorListener { error -> error.printStackTrace() }) { @Throws(AuthFailureError::class) override fun getHeaders(): MutableMap<String, String>{ val params: MutableMap<String, String> = HashMap() params[ConstantValues.XAppApiKey] = APIURLS.XAppApiValue return params } override fun getParams(): Map<String, String>? { val params: MutableMap<String, String> = HashMap() val sharedPreferences: SharedPreferences = requireActivity().getSharedPreferences(Prefs.PREF_NAME, 0) val userId = Prefs.getStringPref(sharedPreferences, Prefs.USER_ID) val userName = Prefs.getStringPref(sharedPreferences, Prefs.FULL_NAME) params["PersonId"] = userId.toString() params["PersonName"] = userName.toString() params["Concern"] = concern params["DateOfHappen"] = selectedDate params["WhereHappened"] = WDH params["Ext"] = fileExtention return params } override fun getByteData(): Map<String, DataPart>? { val params: MutableMap<String, DataPart> = HashMap() // file name could found file base or direct access from real path // for now just get bitmap data from ImageView params["cover"] = DataPart( "sd.pdf", byteArray, "doc/pdf" ) return params } } AppSingleton.getInstance(requireContext())?.addToRequestQueue(multipartRequest) } 

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.