Multipart Request using Retrofit 1.8.0 not working

12,844

Solution 1

I did this instead for the interface

interface MultipartFormDataService {
    @POST("/{uploadPath}")
    void multipartFormDataSend(
            @EncodedPath("uploadPath") String uploadPath,
            @Body MultipartTypedOutput multipartTypedOutput,
            Callback<String> cb);
}

Then later when I call it, it looks like this

// creating the Multipart body using retrofit
MultipartTypedOutput multipartTypedOutput = new MultipartTypedOutput();
TypedString idParam = new TypedString("[ID Value]")
TypedString bodyParam = new TypedString("[Body text]")
ByteArrayTypedOutput byteMultipartTypedOut = new ByteArrayTypedOutput(bytes)

// add parts
multipartTypedOutput.addPart("id", idParam);
multipartTypedOutput.addPart("body", bodyParam);
multipartTypedOutput.addPart("attachment", extraParamTypedString);

// send
multipartService.multipartFormDataSend(
                "[TARGET URL]",
                multipartTypedOutput,
            aCallback);

My ByteArrayTypedOutput was simple

public class ByteArrayTypedOutput implements TypedOutput {

    private MultipartFormMetadata metadata;
    private byte[] imageData;

    public ByteArrayTypedOutput(MultipartFormMetadata metadata, byte[] imageData)
        this.metadata = metadata;
        this.imageData = imageData;
    }

    @Override
    public String fileName() {
        return metadata.fileName;
    }

    @Override
    public String mimeType() {
        return metadata.fileMimeType;
    }

    @Override
    public long length() {
        return imageData.length;
    }

    @Override
    public void writeTo(OutputStream outputStream) throws IOException {
         outputStream.write(imageData);
    }
}

Solution 2

If you see the examples on http://square.github.io/retrofit/ the object types for your "id" and "part[body]" parameters need to be TypedString and not String. TypedString sets the appropriate MIME type and does the conversion to bytes:

https://github.com/square/retrofit/blob/master/retrofit/src/main/java/retrofit/mime/TypedString.java

Share:
12,844
Anel Rojas Hernández
Author by

Anel Rojas Hernández

Full-stack developer Since 2010, I have worked on building native mobile applications iOS and Android. Since 2019, I have started working on frontend and backend side using Ruby on Rails, Vue.js, Docker Tech stack: iOS (Swift - SwiftUI - Objective-C) Android (Kotlin - Java) Ruby on Rails Vue.js Docker Postgres Services I have used: Firebase for mobile apps Google Analytics AWS (Store images, Lambda, SQS)

Updated on June 17, 2022

Comments

  • Anel Rojas Hernández
    Anel Rojas Hernández almost 2 years

    I have like 4 days, trying to make a Multipart Request using Retrofit 1.8.0 in android with any success. My interface looks something like this

    @Multipart
    @POST("/posts/add.json") 
    void addComment(
      @Part("id") String id,
      @Part("post[body]") String body,
      @Part("post[attachment]") TypedFile attachment,
      Callback<Map<String, String>> callback );
    

    But, in the server side, I receive the following

    Parameters: {"id"=># <File:/var/folders/z0/0ggjvvfj4t1fdsvbxf3lc9pw0000gn/T/RackMultipart9853-0>, "post"=>{"body"=>#<File:/var/folders/z0/0ggjvvfj4t1fdsvbxf3lc9pw0000gn/T/RackMultipart9853-1>, "attachment"=>#<File:/var/folders/z0/0ggjvvfj4t1fdsvbxf3lc9pw0000gn/T/RackMultipart9853-2>}, "controller"=>"posts", "action"=>"add", "format"=>"json"}
    

    As you can see, the file part is sending it in every part but and I'm missing the parameters' value of id and post[body]

    Here it's what Retrofit is trying to send

     02-06 15:01:16.213    32545-822/com.myapp D/Retrofit﹕ --fe41634b-6826-4ee4-95cb-65efb0ca66c2
    Content-Disposition: form-data; name="id"
    Content-Type: text/plain; charset=UTF-8
    Content-Length: 3
    Content-Transfer-Encoding: binary
    189
    --fe41634b-6826-4ee4-95cb-65efb0ca66c2
    Content-Disposition: form-data; name="post[body]"
    Content-Type: text/plain; charset=UTF-8
    Content-Length: 4
    Content-Transfer-Encoding: binary
    test
    --fe41634b-6826-4ee4-95cb-65efb0ca66c2
    Content-Disposition: form-data; name="post[attachment]"; filename="IMG_20140203_144358.jpg"
    Content-Type: image/jpg
    Content-Length: 1615460
    Content-Transfer-Encoding: binary
    ����/�Exif����MM��*���������
    

    Here it is what the HttpMime library is sending in the Multipart, the difference is the "Content-Transfer-Encoding" header against Retrofit

    Content-Disposition: form-data; name="id"
    Content-Type: text/plain; charset=US-ASCII
    Content-Transfer-Encoding: 8bit
    
    Content-Disposition: form-data; name=“post[body]"
    Content-Type: text/plain; charset=US-ASCII
    Content-Transfer-Encoding: 8bit
    
    Content-Disposition: form-data; name=“post[attachment]"; filename="images.jpg"
    Content-Type: image/jpg
    Content-Transfer-Encoding: binary
    

    Any clue? Thanks in advance

    -------------------------------SOLUTION----------------------------------

    At the end, I resolved this way, actually my answer is pretty close to @lazypig, it was a good guideline

    The only thing that I changed was his class "ByteArrayTypedOutput"

    I created a class called "MultipartTypedOutputCustom" http://pastie.org/10549360

    And this is, how it looks my interface now

    "PostsRetrofitAPI.java" class

    @POST("/posts/add.json")
        void addComment(@Body MultipartTypedOutputCustom parts,
                        Callback<Map<String, String>> callback);
    

    "PostsService.java"class

    //Properties
    private PostsRetrofitAPI mApi;
    ...
    
        @Override
            public void addComment(ServiceResponseHandler<Map<String, String>> handler, String id, String body, TypedFile attachment) {
               MultipartTypedOutputCustom parts = new MultipartTypedOutputCustom();
               parts.addPart("id", new TypedString(id));
               parts.addPart("post[body]", new TypedString(body));
               parts.addPart("post[attachment]", attachment);
        objectRetrofitCallback= new ObjectRetrofitCallback(handler, ServerError.class, ClientError.class);
                mApi.addComment(parts, objectRetrofitCallback);
            }