36

What are my options for uploading a single large file (more specifically, to s3) in multipart in Android using OKhttp?

rink.attendant.6
  • 44,500
  • 61
  • 101
  • 156
Tomer Weller
  • 2,812
  • 3
  • 26
  • 26

5 Answers5

43

Get OkHttp 2.1, and use MultipartBuilder.addFormDataPart() which takes the filename as a parameter.

       /**
         * Upload Image
         *
         * @param memberId
         * @param sourceImageFile
         * @return
         */
        public static JSONObject uploadImage(String memberId, String sourceImageFile) {
    
            try {
                File sourceFile = new File(sourceImageFile);
    
                Log.d(TAG, "File...::::" + sourceFile + " : " + sourceFile.exists());
             //Determining the media type        
             final MediaType MEDIA_TYPE = sourceImageFile.endsWith("png") ? 

                MediaType.parse("image/png") : MediaType.parse("image/jpeg");
                
    
                RequestBody requestBody = new MultipartBuilder()
                        .type(MultipartBuilder.FORM)
                        .addFormDataPart("member_id", memberId)
                        .addFormDataPart("file", "profile.png", RequestBody.create(MEDIA_TYPE, sourceFile))
                        .build();
    
                Request request = new Request.Builder()
                        .url(URL_UPLOAD_IMAGE)
                        .post(requestBody)
                        .build();
    
                OkHttpClient client = new OkHttpClient();
                Response response = client.newCall(request).execute();
                return new JSONObject(response.body().string());
    
            } catch (UnknownHostException | UnsupportedEncodingException e) {
                Log.e(TAG, "Error: " + e.getLocalizedMessage());
            } catch (Exception e) {
                Log.e(TAG, "Other Error: " + e.getLocalizedMessage());
            }
            return null;
        }

#Edited for okhttp3:

compile 'com.squareup.okhttp3:okhttp:3.4.1'

RequestBody replaced by:

RequestBody requestBody = new MultipartBody.Builder()
                    .setType(MultipartBody.FORM)
                    .addFormDataPart("uploaded_file", filename, RequestBody.create(MEDIA_TYPE_PNG, sourceFile))
                    .addFormDataPart("result", "my_image")
                    .build();

#Uploaded Demo on GITHUB: ##I have added my answer for Multiple Image Upload :)

Adam Davis
  • 379
  • 5
  • 16
Pratik Butani
  • 60,504
  • 58
  • 273
  • 437
40

From the OkHttp Recipes page, this code uploads an image to Imgur:

private static final String IMGUR_CLIENT_ID = "...";
private static final MediaType MEDIA_TYPE_PNG = MediaType.parse("image/png");

private final OkHttpClient client = new OkHttpClient();

public void run() throws Exception {
  // Use the imgur image upload API as documented at https://api.imgur.com/endpoints/image
  RequestBody requestBody = new MultipartBuilder()
      .type(MultipartBuilder.FORM)
      .addPart(
          Headers.of("Content-Disposition", "form-data; name=\"title\""),
          RequestBody.create(null, "Square Logo"))
      .addPart(
          Headers.of("Content-Disposition", "form-data; name=\"image\""),
          RequestBody.create(MEDIA_TYPE_PNG, new File("website/static/logo-square.png")))
      .build();

  Request request = new Request.Builder()
      .header("Authorization", "Client-ID " + IMGUR_CLIENT_ID)
      .url("https://api.imgur.com/3/image")
      .post(requestBody)
      .build();

  Response response = client.newCall(request).execute();
  if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);

  System.out.println(response.body().string());
}

You'll need to adapt this to S3, but the classes you need should be the same.

Jesse Wilson
  • 39,078
  • 8
  • 121
  • 128
  • Thanks Jesse. Is there any way to track progress of the request assuming I know the content length? – Tomer Weller Jun 18 '14 at 13:29
  • 6
    @TomerWeller you can create a custom RequestBody that tracks bytes moved. You'll want to wrap the Sink passed to the RequestBody. – Jesse Wilson Jun 19 '14 at 04:27
  • 1
    Tomer, see my answer here: http://stackoverflow.com/questions/25962595/tracking-progress-of-multipart-file-upload-using-okhttp/26376724#26376724 – Eduard B. Oct 15 '14 at 07:38
  • If I have the image in byte[] I tried doing this: `addPart(Headers.of("..."), RequestBody.create(MEDIA_TYPE_PNG, imageBytes))`; But this doesn't work. It puts the byte data in the headers instead of the body. Any idea why? – b.lyte Mar 24 '15 at 18:18
  • 1
    I figured it out. With imgur, you don't have to provide a filename, so sending the image bytes like I did in the above comment worked. However, for the API I'm actually talking to, it expects a filename so this worked: `multipartBuilder.addFormDataPart("image", fileName, entry.getValue());` – b.lyte Mar 24 '15 at 18:40
  • That is great, but you can also add the attributes to POST more easily using: .addFormDataPart("file", "profile.png", RequestBody.create(MEDIA_TYPE_PNG, sourceFile)) and .addFormDataPart("id", "1") for string attribute – sandino Jan 14 '16 at 13:52
  • 5
    MultiPartBuilder is called MultipartBody.Builder now, see: http://stackoverflow.com/questions/34676044/multipartbuilder-cant-be-resolved-in-okhttp3-0-0-rc1 – Warpzit May 31 '16 at 05:22
  • Hi buddies, for uploading image is it good to use asynchronous or not? you know, I don't understand what is the difference between asynchronous and synchronous.... – jsina Jun 18 '16 at 10:04
  • Hi guys, please answer this question I am struck here https://stackoverflow.com/questions/52520520/how-to-add-list-of-object-i-e-userdata-type-to-multipartbody-in-okhttpclient – Rahul Sep 27 '18 at 06:00
  • 1
    The link is broken to recipe page. Here is the link: https://github.com/square/okhttp/blob/master/samples/guide/src/main/java/okhttp3/recipes/PostMultipart.java – Jatin Mar 01 '21 at 04:02
  • This fails to load a large file and causes an OOM exception – behelit Dec 03 '21 at 08:13
  • To deal with large files and `RequestBody` use the Okio library. I provided an example here https://stackoverflow.com/a/68481022/2807177 – gmanjon Jan 31 '22 at 12:25
7

For okhttp 4.* use the MultipartBody.Builder:

fun postMultipart(url: String, text: String, imagePath: String, imageFileName: String): okhttp3.Response? {
    val file = File(imagePath)
    val fileRequestBody = file.asRequestBody("image/jpeg".toMediaType())
    val requestBody = MultipartBody.Builder()
        .addFormDataPart("text", text)
        .addFormDataPart("image", imageFileName, fileRequestBody)
        .build()

    val request = getRequestBuilder(url)
        .post(requestBody)
        .build()

    val client = OkHttpClient()
    client.newCall(request).execute().use { response ->
        return response
    }
}
Florian Moser
  • 2,583
  • 1
  • 30
  • 40
  • Is `Multipart` the one that makes uploading a large file possible? I am using `RequestBody` and seems like I am unable to upload a very large file. – abhiarora Jan 27 '22 at 18:36
  • To deal with large files use the Okio library. I provided an example here https://stackoverflow.com/a/68481022/2807177 – gmanjon Jan 31 '22 at 12:25
2

for okhttp 2.6.0 {

    try {
        File file = new File(Environment.getExternalStorageDirectory().getPath()+"/xxx/share/" + "ic_launcher.png");
        String contentType = file.toURL().openConnection().getContentType();
        RequestBody fileBody = RequestBody.create(MediaType.parse(contentType), file);

        RequestBody requestBody = new MultipartBuilder()
                .type(MultipartBuilder.FORM)
                .addFormDataPart("fileUploadType","1")
                .addFormDataPart("miniType",contentType)
                .addFormDataPart("ext",file.getAbsolutePath().substring(file.getAbsolutePath().lastIndexOf(".")))
                .addFormDataPart("fileTypeName","img")
                .addFormDataPart("Filedata","ss.png",fileBody)
                .build();
        Request request = new Request.Builder()
                .url(Contains.MULTIPARTY_POST)
                .post(requestBody)
                .build();
        okHttpClient.newCall(request).enqueue(new Callback() {
            @Override
            public void onFailure(Request request, IOException e) {
                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        tvGetNews.setText("upload fail");
                    }
                });
            }

            @Override
            public void onResponse(Response response) throws IOException {
                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        tvGetNews.setText("upload success");
                    }
                });
            }
        });
    } catch (IOException e) {
        e.printStackTrace();
    }

}
yabin ya
  • 7,283
  • 2
  • 11
  • 6
  • Is `Multipart` the one that makes uploading a large file possible? I am using `RequestBody` and seems like I am unable to upload a very large file. – abhiarora Jan 27 '22 at 18:37
  • To deal with large files and `RequestBody` use the Okio library. I provided an example here https://stackoverflow.com/a/68481022/2807177 – gmanjon Jan 31 '22 at 12:26
1

In Android you usally part from an Uri. The problem when using large files is that you easily run into OutOfMemoryError if you try to read the full stream to a byte array (everything in memory) or you end up creating useless files with Uri stream. This is because RequestBody doesn't support creation from Stream (because sometimes OkHttp needs to read it many times, if you get a 30X redirect for instance) or Uri (because OkHttp is not an Android library).

But OkHttp provides the library Okio, with convenient classes emulating Streams (Source and Sink) and more convenient internal usage.

So, to create a BodyRequest form an Uri avoiding any OutOfMemoryError due to large files create it this way:

private static final MediaType MULTIPART_FOR_DATA = MediaType.parse("multipart/form-data");

private @NotNull RequestBody getFilePart(Uri largeFileUri) {

        return new RequestBody() {
            @Override
            public MediaType contentType() {
                return MULTIPART_FOR_DATA;
            }

            @Override
            public void writeTo(@NotNull BufferedSink sink) throws IOException {
                try (Source source = Okio.source(context.getContentResolver().openInputStream(mediaUri))) {
                    sink.writeAll(source);
                }
            }
        };
    }

Thank you to everyone posting and commenting in the folowing GitHub thread https://github.com/square/okhttp/issues/3585

gmanjon
  • 1,483
  • 1
  • 12
  • 16