Using a mix of the code in related answers, I managed to make a call with multipart. However, I'm failing to send it with the correct parameter name.
How the request must be (taken from the iOS app):
How my request looks:
Code:
MultipartRequest is supposed to be a base multipart request.
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Matrix;
import android.graphics.RectF;
import android.util.Log;
import com.android.volley.Request;
import com.android.volley.Response;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
import java.io.File;
import com.android.volley.AuthFailureError;
import com.android.volley.toolbox.StringRequest;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
public class MultipartRequest extends StringRequest {
private final int maxImageWidth = 200;
private final int maxImageHeight = 200;
static String mimeType;
private final File file;
DataOutputStream dos = null;
String lineEnd = "\r\n";
static String boundary = "apiclient-" + System.currentTimeMillis();
String twoHyphens = "--";
int bytesRead, bytesAvailable, bufferSize;
byte[] buffer;
int maxBufferSize = 124 * 124;
public static MultipartRequest newInstance(final PlayEarnAPIImpl.OnPlayEarnAPIResponse listener, File file, Response.Listener<String> responseListener, Response.ErrorListener errorListener, String serviceURL) {
mimeType = "multipart/form-data;boundary=" + boundary;
return new MultipartRequest(Request.Method.PUT, serviceURL, responseListener, errorListener, file, serviceURL);
}
public MultipartRequest(int method, String url, Response.Listener<String> listener, Response.ErrorListener errorListener, File file, String serviceURL) {
super(method, url, listener, errorListener);
this.file = file;
}
private byte[] decodeFile(File file) {
BitmapFactory.Options options = new BitmapFactory.Options();
options.inPreferredConfig = Bitmap.Config.ARGB_8888;
options.inSampleSize = calculateInSampleSize(options, 400, 400);
Bitmap bitmap = BitmapFactory.decodeFile(file.getAbsolutePath(), options);
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
bitmap.compress(Bitmap.CompressFormat.PNG, 100, byteArrayOutputStream);
return byteArrayOutputStream.toByteArray();
}
public static int calculateInSampleSize(
BitmapFactory.Options options, int reqWidth, int reqHeight) {
// Raw height and width of image
final int height = options.outHeight;
final int width = options.outWidth;
int inSampleSize = 1;
if (height > reqHeight || width > reqWidth) {
final int halfHeight = height / 2;
final int halfWidth = width / 2;
// Calculate the largest inSampleSize value that is a power of 2 and keeps both
// height and width larger than the requested height and width.
while ((halfHeight / inSampleSize) > reqHeight
&& (halfWidth / inSampleSize) > reqWidth) {
inSampleSize *= 2;
}
}
return inSampleSize;
}
//solution 2
private byte[] shrinkImage(File file) {
try {
int inWidth = 0;
int inHeight = 0;
InputStream in = new FileInputStream(file.getAbsolutePath());
// decode image size (decode metadata only, not the whole image)
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeStream(in, null, options);
in.close();
in = null;
// save width and height
inWidth = options.outWidth;
inHeight = options.outHeight;
// decode full image pre-resized
in = new FileInputStream(file.getAbsolutePath());
options = new BitmapFactory.Options();
// calc rought re-size (this is no exact resize)
options.inSampleSize = Math.max(inWidth / maxImageWidth, inHeight / maxImageHeight);
// decode full image
Bitmap roughBitmap = BitmapFactory.decodeStream(in, null, options);
// calc exact destination size
Matrix m = new Matrix();
RectF inRect = new RectF(0, 0, roughBitmap.getWidth(), roughBitmap.getHeight());
RectF outRect = new RectF(0, 0, maxImageWidth, maxImageHeight);
m.setRectToRect(inRect, outRect, Matrix.ScaleToFit.CENTER);
float[] values = new float[9];
m.getValues(values);
// resize bitmap
Bitmap resizedBitmap = Bitmap.createScaledBitmap(roughBitmap, (int) (roughBitmap.getWidth() * values[0]), (int) (roughBitmap.getHeight() * values[4]), true);
// save image
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
resizedBitmap.compress(Bitmap.CompressFormat.PNG, 100, byteArrayOutputStream);
return byteArrayOutputStream.toByteArray();
} catch (IOException e) {
Log.e("Image", e.getMessage(), e);
}
return null;
}
@Override
public String getBodyContentType() {
return mimeType;
}
@Override
public byte[] getBody() throws AuthFailureError {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
dos = new DataOutputStream(bos);
byte[] bitmapData = null;
try {
//dos.writeBytes(twoHyphens + boundary + lineEnd);
dos.writeBytes(lineEnd);
dos.writeBytes(" ------------------12345");
dos.writeBytes(lineEnd);
dos.writeBytes("Content-Disposition: form-data; name=\"picture\"; filename=\"file.png\"");
dos.writeBytes(lineEnd);
dos.writeBytes("Content-Type: image/png");
//dos.writeBytes("Content-Disposition: form-data; name=\"picture\";filename=\""
// + file.getName() + "\"" + lineEnd);
dos.writeBytes(lineEnd);
bitmapData = shrinkImage(this.file);
ByteArrayInputStream fileInputStream = new ByteArrayInputStream(bitmapData);
bytesAvailable = fileInputStream.available();
bufferSize = Math.min(bytesAvailable, maxBufferSize);
Log.d("MultipartRequest", "bufferSize:" + bufferSize);
buffer = new byte[bufferSize];
// read file and write it into form...
bytesRead = fileInputStream.read(buffer, 0, bufferSize);
while (bytesRead > 0) {
dos.write(buffer, 0, bufferSize);
bytesAvailable = fileInputStream.available();
bufferSize = Math.min(bytesAvailable, maxBufferSize);
bytesRead = fileInputStream.read(buffer, 0, bufferSize);
}
// send multipart form data necesssary after file data...
dos.writeBytes(lineEnd);
dos.writeBytes(twoHyphens + boundary + twoHyphens + lineEnd);
return bos.toByteArray();
} catch (IOException e) {
e.printStackTrace();
}
return bitmapData;
}
This is the request I'm using:
import com.android.volley.AuthFailureError;
import com.android.volley.Request;
import com.android.volley.Response;
import com.android.volley.VolleyError;
import com.google.gson.Gson;
import java.io.File;
import java.util.Map;
public class ChangeProfileImageRequest extends MultipartRequest {
private static final String UPDATE_PATH = "users/update";
String serviceURL = APIImpl.API_URL + UPDATE_PATH;
public static ChangeProfileImageRequest newInstance(final APIImpl.OnAPIResponse listener, File file) {
String serviceURL = APIImpl.API_URL + UPDATE_PATH;
return new ChangeProfileImageRequest(Request.Method.PUT, serviceURL, new ResponseListener(listener), new ErrorListener(listener), file, serviceURL);
}
public ChangeProfileImageRequest(int method, String url, Response.Listener<String> listener, Response.ErrorListener errorListener, File file, String serviceURL) {
super(method, url, listener, errorListener, file, serviceURL);
}
@Override
public Map<String, String> getHeaders() throws AuthFailureError {
Map<String, String> headers = new APIImpl().getTokenHeader();
//headers.put("Accept", "*/*");
headers.put("Content-Type", "multipart/form-data;boundary=----------------12345");
AppController.getInstance().addSessionCookie(headers);
return headers;
}
So I guess it has something to do with the screenshot that says "Failed to decode multipart body", but I don't know what's the specific problem. Also from server side the picture parameter is not being received.