Retrofit 2 — Passing Multiple Parts Along a File with @PartMap

In previous tutorial, we've shown you how to upload files and upload multiple files. So far, we've focused on the file part of multipart requests. In this tutorial, we'll concentrate on the data that goes along with the request, for example description string(s).

If you don't feel ready for such an advanced topic yet, catch up with our extensive library of Retrofit topics:

Retrofit Series Overview

Multiple Parts with @PartMap

Multipart requests are often used for forms with an additional file. For example, we've utilized it in the past for a feedback form, which also allows the user to upload a photo.

If you just need to pass a single or two descriptions with a file, you can just declare it as a @Part in your service declaration:

public interface FileUploadService {  
    // previous code for single description
    @Multipart
    @POST("upload")
    Call<ResponseBody> uploadFile(
            @Part("description") RequestBody description,
            @Part MultipartBody.Part file);
}

This works great for small use cases, but if you need to send more than a handful of properties, it gets quite messy, especially if not all of them are always set.

Retrofit offers an easy solution, which makes the uploads quite customizable: @PartMap. @PartMap is an additional annotation for a request parameter, which allows us to specify how many and which parts we send during runtime. This can very helpful if your form is very long, but only a few of those input field values are actually send. Instead of declaring an interface method with 20 or more parameters you can use a single @PartMap. Let's see this in action!

First of all, we need to create a new interface method with the @PartMap annotation:

public interface FileUploadService {  
    // declare a description explicitly
    // would need to declare 
    @Multipart
    @POST("upload")
    Call<ResponseBody> uploadFile(
            @Part("description") RequestBody description,
            @Part MultipartBody.Part file);

    @Multipart
    @POST("upload")
    Call<ResponseBody> uploadFileWithPartMap(
            @PartMap() Map<String, RequestBody> partMap,
            @Part MultipartBody.Part file);
}

It's important that you use a Map<String, RequestBody> implementation as a parameter type for the @PartMap part of the request. As we've explained above, this allows you to send a runtime-dependent list of data along with your file. The brackets after the PartMap are optional. You need to use them, if you want to specify the encoding, similar to the content encoding in FieldMaps.

The second part is filling the Map with data. In a previous tutorial, we've introduced two helper methods to create a RequestBody for a String variable and file variable:

@NonNull
private MultipartBody.Part prepareFilePart(String partName, Uri fileUri) {  
    // https://github.com/iPaulPro/aFileChooser/blob/master/aFileChooser/src/com/ipaulpro/afilechooser/utils/FileUtils.java
    // use the FileUtils to get the actual file by uri
    File file = FileUtils.getFile(this, fileUri);

    // create RequestBody instance from file
    RequestBody requestFile =
        RequestBody.create(
            MediaType.parse(getContentResolver().getType(fileUri)), 
            file
        );

    // MultipartBody.Part is used to send also the actual file name
    return MultipartBody.Part.createFormData(partName, file.getName(), requestFile);

Finally, let's use this method and view the entire code from creating the Retrofit service, to filling the request with data and enqueuing the request:

Uri fileUri = ... // from a file chooser or a camera intent

// create upload service client
FileUploadService service =  
        ServiceGenerator.createService(FileUploadService.class);

// create part for file (photo, video, ...)
MultipartBody.Part body = prepareFilePart("photo", fileUri);

// create a map of data to pass along
RequestBody description = createPartFromString("hello, this is description speaking");  
RequestBody place = createPartFromString("Magdeburg");  
RequestBody time = createPartFromString("2016");

HashMap<String, RequestBody> map = new HashMap<>();  
map.put("description", description);  
map.put("place", place);  
map.put("time", time);

// finally, execute the request
Call<ResponseBody> call = service.uploadFileWithPartMap(map, body);  
call.enqueue(...);  

Of course, depending on your use case you've to fill in a bit of logic. If you've a similar scenario to our feedback form, you could go through the list of EditTexts and only add the content of non-empty ones to your multipart request.

Summary

In this tutorial, we've shown you the trick of the PartMap annotation. Retrofit 2 makes it easy to counter overblowingly long method declarations of multipart requests by offering to send multiple parts with @PartMap. We hope you've learned how to send a map of data with your request. If you've any questions, let us know in the comments below.


Explore the Library

Find interesting tutorials and solutions for your problems.