198

I am trying to do a HTTP POST to server using Retrofit 2.0

MediaType MEDIA_TYPE_TEXT = MediaType.parse("text/plain");
MediaType MEDIA_TYPE_IMAGE = MediaType.parse("image/*");

ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
    imageBitmap.compress(Bitmap.CompressFormat.JPEG,90,byteArrayOutputStream);
profilePictureByte = byteArrayOutputStream.toByteArray();

Call<APIResults> call = ServiceAPI.updateProfile(
        RequestBody.create(MEDIA_TYPE_TEXT, emailString),
        RequestBody.create(MEDIA_TYPE_IMAGE, profilePictureByte));

call.enqueue();

The server returns an error saying the file is not valid.

This is weird because I have tried to upload the same file with the same format on iOS(using other library), but it uploads successfully.

I am wondering what is the proper way to upload an image using Retrofit 2.0?

Should I save it to disk first before uploading?

P.S.: I have used retrofit for other Multipart request that does not include image and they completed successfully. The problem is when I am trying to include a byte to the body.

Samuel Philipp
  • 10,631
  • 12
  • 36
  • 56
JayVDiyk
  • 4,277
  • 22
  • 70
  • 135

13 Answers13

223

There is a correct way of uploading a file with its name with Retrofit 2, without any hack:

Define API interface:

@Multipart
@POST("uploadAttachment")
Call<MyResponse> uploadAttachment(@Part MultipartBody.Part filePart); 
                                   // You can add other parameters too

Upload file like this:

File file = // initialize file here

MultipartBody.Part filePart = MultipartBody.Part.createFormData("file", file.getName(), RequestBody.create(MediaType.parse("image/*"), file));

Call<MyResponse> call = api.uploadAttachment(filePart);

This demonstrates only file uploading, you can also add other parameters in the same method with @Part annotation.

jimmy0251
  • 16,293
  • 10
  • 36
  • 39
  • 4
    how can we send multiple files using MultipartBody.Part? – Praveen Sharma Sep 20 '16 at 07:05
  • You can use multiple `MultipartBody.Part` arguments in the same API. – jimmy0251 Sep 20 '16 at 08:17
  • 1
    I need to send collections of images with "image[]" as a key. I tried `@Part("images[]") List images` but it gives error that `@Part parameters using the MultipartBody.Part must not include a part name` – Praveen Sharma Sep 20 '16 at 10:59
  • You should use `@Body MultipartBody multipartBody` and `MultipartBody.Builder` for sending collection of images. – jimmy0251 Sep 20 '16 at 11:02
  • 3
    how i can added key to mutipart – andro Nov 18 '16 at 14:44
  • @jimmy0251 hello I want to switch from Volley to Retrofit. What about audio and video. In Volley, we have this conversion of bytes... how about in Retrofit, how do you pass audio and video to server? Thanks a lot. – Woppi Jan 24 '17 at 09:43
  • You can upload any file with the above method, just use appropriate mimetype of the File. – jimmy0251 Jan 24 '17 at 09:56
  • @jimmy0251 can you please look at my question https://stackoverflow.com/questions/44225188/retrofit-multipart-with-body/44225311?noredirect=1#comment75461322_44225311? – jlively May 28 '17 at 10:13
  • This should be marked as the correct answer as it does the job with less and simpler code. – Farrukh Najmi Aug 16 '17 at 04:36
  • I'm still getting : `{"statusCode":400,"field":"media","type":"isValidMime","error":"media - Unsopported file format","generatedError":"media - Unsopported file format"}` – Kishore Jethava Jun 28 '18 at 06:03
  • @jimmy0251 getting error if i use chinease name for file.getfileName(), app getting crash showing error form-data – aj0822ArpitJoshi Sep 30 '19 at 06:58
  • 1
    @jimmy0251 can help me with this `@Part("channel[comment][image]")` . i have to upload image on this `channel[comment][image]` key . how can i do that. – Rahul sharma Feb 20 '20 at 06:23
  • how do you initialize an image file with just the uri in your answer? – Mahmoud Omara Jun 25 '20 at 14:45
  • receiving file type on server is `image/* ` instead of `image/jpg` or `image/png`. etc.. – mufazmi Nov 25 '20 at 15:00
  • This is crucial, how do we use this with an `images[]` key? – dessalines Jan 17 '22 at 18:34
  • This is an extremely helpful answer! Can confirm the resulting form is parsed correctly by Flask. – Arthur Khazbs Jul 05 '23 at 09:30
208

I am highlighting the solution in both 1.9 and 2.0 since it is useful for some

In 1.9, I think the better solution is to save the file to disk and use it as Typed file like:

RetroFit 1.9

(I don't know about your server-side implementation) have an API interface method similar to this

@POST("/en/Api/Results/UploadFile")
void UploadFile(@Part("file") TypedFile file,
                @Part("folder") String folder,
                Callback<Response> callback);

And use it like

TypedFile file = new TypedFile("multipart/form-data",
                                       new File(path));

For RetroFit 2 Use the following method

RetroFit 2.0 ( This was a workaround for an issue in RetroFit 2 which is fixed now, for the correct method refer jimmy0251's answer)

API Interface:

public interface ApiInterface {

    @Multipart
    @POST("/api/Accounts/editaccount")
    Call<User> editUser(@Header("Authorization") String authorization,
                        @Part("file\"; filename=\"pp.png\" ") RequestBody file,
                        @Part("FirstName") RequestBody fname,
                        @Part("Id") RequestBody id);
}

Use it like:

File file = new File(imageUri.getPath());

RequestBody fbody = RequestBody.create(MediaType.parse("image/*"),
                                       file);

RequestBody name = RequestBody.create(MediaType.parse("text/plain"),
                                      firstNameField.getText()
                                                    .toString());

RequestBody id = RequestBody.create(MediaType.parse("text/plain"),
                                    AZUtils.getUserId(this));

Call<User> call = client.editUser(AZUtils.getToken(this),
                                  fbody,
                                  name,
                                  id);

call.enqueue(new Callback<User>() {

    @Override
    public void onResponse(retrofit.Response<User> response,
                           Retrofit retrofit) {

        AZUtils.printObject(response.body());
    }

    @Override
    public void onFailure(Throwable t) {

        t.printStackTrace();
    }
});
DragonFire
  • 3,722
  • 2
  • 38
  • 51
insomniac
  • 11,146
  • 6
  • 44
  • 55
  • `TypedFile` is not supported/included in Retrofit 2.0 – JayVDiyk Jan 02 '16 at 05:38
  • 5
    Yes,well I think it is an issue (https://github.com/square/retrofit/issues/1063) with retrofit 2.0,you might wanna stick with 1.9 – insomniac Jan 02 '16 at 05:40
  • Yes, that is why I am wondering if there is any other solution other than saving the byte to a `File`. Since it is an extra Permission needed and does not seem to be efficient, "performancely" speaking. Thanks for answering though. – JayVDiyk Jan 02 '16 at 05:42
  • 2
    see my edit ,I have not tryed it yet,you are welcomed to – insomniac Jan 02 '16 at 05:44
  • I have decided to use the approach you posted. Thank you – JayVDiyk Jan 02 '16 at 06:12
  • Tried uploading image file using Retrofit 2.0 but it didn't work for me. Has anyone successfully uploaded file using Retrofit 2.0 – Sagar Trehan Jan 15 '16 at 21:33
  • 1
    I have successfylly uploaded an image using de Retrofit 2.0 example. – jerogaren Apr 15 '16 at 11:11
  • `@Part("file\"; filename=\"pp.png\" ")` I see the file name is hardcoded here, what if I want to the name to change? – Bhargav Jul 12 '16 at 11:01
  • 3
    @Bhargav You can change the interface to `@Multipart @POST("/api/Accounts/editaccount") Call editUser(@PartMap Map params);` And When you have the file: `Map map = new HashMap<>(); RequestBody fileBody = RequestBody.create(MediaType.parse("image/jpg"), file); map.put("file\"; filename=\"" + file.getName(), fileBody);` – insomniac Jul 12 '16 at 11:34
  • 2
    @insomniac Yes I just found out about that, can also use `MultiPartBody.Part` – Bhargav Jul 12 '16 at 11:53
  • You didn't explain your answer here – Daniel Viglione Aug 29 '17 at 00:18
  • What more explanation do you need Donato? The one who asked the question is familiar with Retrofit and satisfied with the answer so did other 86 people, What more do you need? – insomniac Aug 29 '17 at 05:03
  • This is a hack. It's been fixed in retrofit 2 for a long time by using MultipartBody.Part parameters in your retrofit service. See @jimmy0251 answer below – Matt Wolfe Dec 01 '17 at 17:40
  • PayloadTooLargeError: request entity too large – Iman Marashi Apr 19 '20 at 07:16
25

I used Retrofit 2.0 for my register users, send multipart/form File image and text from register account

In my RegisterActivity, use an AsyncTask

//AsyncTask
private class Register extends AsyncTask<String, Void, String> {

    @Override
    protected void onPreExecute() {..}

    @Override
    protected String doInBackground(String... params) {
        new com.tequilasoft.mesasderegalos.dbo.Register().register(txtNombres, selectedImagePath, txtEmail, txtPassword);
        responseMensaje = StaticValues.mensaje ;
        mensajeCodigo = StaticValues.mensajeCodigo;
        return String.valueOf(StaticValues.code);
    }

    @Override
    protected void onPostExecute(String codeResult) {..}

And in my Register.java class is where use Retrofit with synchronous call

import android.util.Log;
import com.tequilasoft.mesasderegalos.interfaces.RegisterService;
import com.tequilasoft.mesasderegalos.utils.StaticValues;
import com.tequilasoft.mesasderegalos.utils.Utilities;
import java.io.File;
import okhttp3.MediaType;
import okhttp3.MultipartBody;
import okhttp3.RequestBody;
import okhttp3.ResponseBody;
import retrofit2.Call; 
import retrofit2.Response;
/**Created by sam on 2/09/16.*/
public class Register {

public void register(String nombres, String selectedImagePath, String email, String password){

    try {
        // create upload service client
        RegisterService service = ServiceGenerator.createUser(RegisterService.class);

        // add another part within the multipart request
        RequestBody requestEmail =
                RequestBody.create(
                        MediaType.parse("multipart/form-data"), email);
        // add another part within the multipart request
        RequestBody requestPassword =
                RequestBody.create(
                        MediaType.parse("multipart/form-data"), password);
        // add another part within the multipart request
        RequestBody requestNombres =
                RequestBody.create(
                        MediaType.parse("multipart/form-data"), nombres);

        MultipartBody.Part imagenPerfil = null;
        if(selectedImagePath!=null){
            File file = new File(selectedImagePath);
            Log.i("Register","Nombre del archivo "+file.getName());
            // create RequestBody instance from file
            RequestBody requestFile =
                    RequestBody.create(MediaType.parse("multipart/form-data"), file);
            // MultipartBody.Part is used to send also the actual file name
            imagenPerfil = MultipartBody.Part.createFormData("imagenPerfil", file.getName(), requestFile);
        }

        // finally, execute the request
        Call<ResponseBody> call = service.registerUser(imagenPerfil, requestEmail,requestPassword,requestNombres);
        Response<ResponseBody> bodyResponse = call.execute();
        StaticValues.code  = bodyResponse.code();
        StaticValues.mensaje  = bodyResponse.message();
        ResponseBody errorBody = bodyResponse.errorBody();
        StaticValues.mensajeCodigo  = errorBody==null
                ?null
                :Utilities.mensajeCodigoDeLaRespuestaJSON(bodyResponse.errorBody().byteStream());
        Log.i("Register","Code "+StaticValues.code);
        Log.i("Register","mensaje "+StaticValues.mensaje);
        Log.i("Register","mensajeCodigo "+StaticValues.mensaje);
    }
    catch (Exception e){
        e.printStackTrace();
    }
}
}

In the interface of RegisterService

public interface RegisterService {
@Multipart
@POST(StaticValues.REGISTER)
Call<ResponseBody> registerUser(@Part MultipartBody.Part image,
                                @Part("email") RequestBody email,
                                @Part("password") RequestBody password,
                                @Part("nombre") RequestBody nombre
);
}

For the Utilities parse ofr InputStream response

public class Utilities {
public static String mensajeCodigoDeLaRespuestaJSON(InputStream inputStream){
    String mensajeCodigo = null;
    try {
        BufferedReader reader = new BufferedReader(
                new InputStreamReader(
                    inputStream, "iso-8859-1"), 8);
        StringBuilder sb = new StringBuilder();
        String line;
        while ((line = reader.readLine()) != null) {
            sb.append(line).append("\n");
        }
        inputStream.close();
        mensajeCodigo = sb.toString();
    } catch (Exception e) {
        Log.e("Buffer Error", "Error converting result " + e.toString());
    }
    return mensajeCodigo;
}
}
Samuel Seda
  • 2,814
  • 1
  • 24
  • 13
22

Update Code for image file uploading in Retrofit2.0

public interface ApiInterface {

    @Multipart
    @POST("user/signup")
    Call<UserModelResponse> updateProfilePhotoProcess(@Part("email") RequestBody email,
                                                      @Part("password") RequestBody password,
                                                      @Part("profile_pic\"; filename=\"pp.png")
                                                              RequestBody file);
}

Change MediaType.parse("image/*") to MediaType.parse("image/jpeg")

RequestBody reqFile = RequestBody.create(MediaType.parse("image/jpeg"),
                                         file);
RequestBody email = RequestBody.create(MediaType.parse("text/plain"),
                                       "upload_test4@gmail.com");
RequestBody password = RequestBody.create(MediaType.parse("text/plain"),
                                          "123456789");

Call<UserModelResponse> call = apiService.updateProfilePhotoProcess(email,
                                                                    password,
                                                                    reqFile);
call.enqueue(new Callback<UserModelResponse>() {

    @Override
    public void onResponse(Call<UserModelResponse> call,
                           Response<UserModelResponse> response) {

        String
                TAG =
                response.body()
                        .toString();

        UserModelResponse userModelResponse = response.body();
        UserModel userModel = userModelResponse.getUserModel();

        Log.d("MainActivity",
              "user image = " + userModel.getProfilePic());

    }

    @Override
    public void onFailure(Call<UserModelResponse> call,
                          Throwable t) {

        Toast.makeText(MainActivity.this,
                       "" + TAG,
                       Toast.LENGTH_LONG)
             .show();

    }
});
Ninja
  • 2,479
  • 3
  • 23
  • 32
Muhammad Waleed
  • 2,517
  • 4
  • 27
  • 75
  • I was trying many ways to do this but I could not get the results. I just changed this ("Change MediaType.parse("image/*") to MediaType.parse("image/jpeg")") like you said and it works now, thank you so much. – Gunnar May 18 '19 at 14:05
  • wish i could give you more than one votes,Thank you. – Rohit Maurya May 24 '19 at 16:14
  • if your api has `@Multipart` then `@Part` annotation must supply a name or use MultipartBody.Part parameter type. – Rohit Jun 28 '19 at 07:35
  • Good solution! And there's one more quote in @Part("profile_pic\"; filename=\"pp.png\" ", it shoule be `@Part("profile_pic\"; filename=\"pp.png "` – Ninja Mar 06 '20 at 13:39
21

So its very simple way to achieve your task. You need to follow below step :-

1. First step

public interface APIService {  
    @Multipart
    @POST("upload")
    Call<ResponseBody> upload(
        @Part("item") RequestBody description,
        @Part("imageNumber") RequestBody description,
        @Part MultipartBody.Part imageFile
    );
}

You need to make the entire call as @Multipart request. item and image number is just string body which is wrapped in RequestBody. We use the MultipartBody.Part class that allows us to send the actual file name besides the binary file data with the request

2. Second step

  File file = (File) params[0];
  RequestBody requestFile = RequestBody.create(MediaType.parse("multipart/form-data"), file);

  MultipartBody.Part body =MultipartBody.Part.createFormData("Image", file.getName(), requestBody);

  RequestBody ItemId = RequestBody.create(okhttp3.MultipartBody.FORM, "22");
  RequestBody ImageNumber = RequestBody.create(okhttp3.MultipartBody.FORM,"1");
  final Call<UploadImageResponse> request = apiService.uploadItemImage(body, ItemId,ImageNumber);

Now you have image path and you need to convert into file.Now convert file into RequestBody using method RequestBody.create(MediaType.parse("multipart/form-data"), file). Now you need to convert your RequestBody requestFile into MultipartBody.Part using method MultipartBody.Part.createFormData("Image", file.getName(), requestBody); .

ImageNumber and ItemId is my another data which I need to send to server so I am also make both thing into RequestBody.

For more info

duggu
  • 37,851
  • 12
  • 116
  • 113
15

Adding to the answer given by @insomniac. You can create a Map to put the parameter for RequestBody including image.

Code for Interface

public interface ApiInterface {
@Multipart
@POST("/api/Accounts/editaccount")
Call<User> editUser (@Header("Authorization") String authorization, @PartMap Map<String, RequestBody> map);
}

Code for Java class

File file = new File(imageUri.getPath());
RequestBody fbody = RequestBody.create(MediaType.parse("image/*"), file);
RequestBody name = RequestBody.create(MediaType.parse("text/plain"), firstNameField.getText().toString());
RequestBody id = RequestBody.create(MediaType.parse("text/plain"), AZUtils.getUserId(this));

Map<String, RequestBody> map = new HashMap<>();
map.put("file\"; filename=\"pp.png\" ", fbody);
map.put("FirstName", name);
map.put("Id", id);
Call<User> call = client.editUser(AZUtils.getToken(this), map);
call.enqueue(new Callback<User>() {
@Override
public void onResponse(retrofit.Response<User> response, Retrofit retrofit) 
{
    AZUtils.printObject(response.body());
}

@Override
public void onFailure(Throwable t) {
    t.printStackTrace();
 }
});
Anjana Sharma
  • 189
  • 1
  • 12
7

in kotlin its quite easy, using extensions methods of toMediaType, asRequestBody and toRequestBody here's an example:

here I am posting a couple of normal fields along with a pdf file and an image file using multipart

this is API declaration using retrofit:

    @Multipart
    @POST("api/Lesson/AddNewLesson")
    fun createLesson(
        @Part("userId") userId: RequestBody,
        @Part("LessonTitle") lessonTitle: RequestBody,
        @Part pdf: MultipartBody.Part,
        @Part imageFile: MultipartBody.Part
    ): Maybe<BaseResponse<String>>

and here is how to actually call it:

api.createLesson(
            userId.toRequestBody("text/plain".toMediaType()),
            lessonTitle.toRequestBody("text/plain".toMediaType()),
            startFromRegister.toString().toRequestBody("text/plain".toMediaType()),
            MultipartBody.Part.createFormData(
                "jpeg",
                imageFile.name,
                imageFile.asRequestBody("image/*".toMediaType())
            ),
            MultipartBody.Part.createFormData(
                "pdf",
                pdfFile.name,
                pdfFile.asRequestBody("application/pdf".toMediaType())
            )
Amin Keshavarzian
  • 3,646
  • 1
  • 37
  • 38
4
* Return MultipartBody from file path

 public static MultipartBody.Part generateFileBody(String imagePath)
    {
        File file = new File(imagePath);
        RequestBody requestFile = RequestBody.create(MediaType.parse("multipart/form-data"), file);
        return MultipartBody.Part.createFormData("mediaProfilePic", file.getName(), requestFile);
    }
Praful Parmar
  • 213
  • 2
  • 6
3

Uploading Files using Retrofit is Quite Simple You need to build your api interface as

public interface Api {

    String BASE_URL = "http://192.168.43.124/ImageUploadApi/";


    @Multipart
    @POST("yourapipath")
    Call<MyResponse> uploadImage(@Part("image\"; filename=\"myfile.jpg\" ") RequestBody file, @Part("desc") RequestBody desc);

}

in the above code image is the key name so if you are using php you will write $_FILES['image']['tmp_name'] to get this. And filename="myfile.jpg" is the name of your file that is being sent with the request.

Now to upload the file you need a method that will give you the absolute path from the Uri.

private String getRealPathFromURI(Uri contentUri) {
    String[] proj = {MediaStore.Images.Media.DATA};
    CursorLoader loader = new CursorLoader(this, contentUri, proj, null, null, null);
    Cursor cursor = loader.loadInBackground();
    int column_index = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA);
    cursor.moveToFirst();
    String result = cursor.getString(column_index);
    cursor.close();
    return result;
}

Now you can use the below code to upload your file.

 private void uploadFile(Uri fileUri, String desc) {

        //creating a file
        File file = new File(getRealPathFromURI(fileUri));

        //creating request body for file
        RequestBody requestFile = RequestBody.create(MediaType.parse(getContentResolver().getType(fileUri)), file);
        RequestBody descBody = RequestBody.create(MediaType.parse("text/plain"), desc);

        //The gson builder
        Gson gson = new GsonBuilder()
                .setLenient()
                .create();


        //creating retrofit object
        Retrofit retrofit = new Retrofit.Builder()
                .baseUrl(Api.BASE_URL)
                .addConverterFactory(GsonConverterFactory.create(gson))
                .build();

        //creating our api 
        Api api = retrofit.create(Api.class);

        //creating a call and calling the upload image method 
        Call<MyResponse> call = api.uploadImage(requestFile, descBody);

        //finally performing the call 
        call.enqueue(new Callback<MyResponse>() {
            @Override
            public void onResponse(Call<MyResponse> call, Response<MyResponse> response) {
                if (!response.body().error) {
                    Toast.makeText(getApplicationContext(), "File Uploaded Successfully...", Toast.LENGTH_LONG).show();
                } else {
                    Toast.makeText(getApplicationContext(), "Some error occurred...", Toast.LENGTH_LONG).show();
                }
            }

            @Override
            public void onFailure(Call<MyResponse> call, Throwable t) {
                Toast.makeText(getApplicationContext(), t.getMessage(), Toast.LENGTH_LONG).show();
            }
        });
    }

For more detailed explanation you can visit this Retrofit Upload File Tutorial.

Belal Khan
  • 2,099
  • 2
  • 22
  • 32
2

Kotlin version with update for deprication of RequestBody.create :

Retrofit interface

@Multipart
@POST("uploadPhoto")
fun uploadFile(@Part file: MultipartBody.Part): Call<FileResponse>

and to Upload

fun uploadFile(fileUrl: String){
    val file = File(fileUrl)
    val fileUploadService = RetrofitClientInstance.retrofitInstance.create(FileUploadService::class.java)
    val requestBody = file.asRequestBody(file.extension.toMediaTypeOrNull())
    val filePart = MultipartBody.Part.createFormData(
        "blob",file.name,requestBody
    )
    val call = fileUploadService.uploadFile(filePart)

    call.enqueue(object: Callback<FileResponse>{
        override fun onFailure(call: Call<FileResponse>, t: Throwable) {
            Log.d(TAG,"Fckd")
        }

        override fun onResponse(call: Call<FileResponse>, response: Response<FileResponse>) {
            Log.d(TAG,"success"+response.toString()+" "+response.body().toString()+"  "+response.body()?.status)
        }

    })
}

Thanks to @jimmy0251

hushed_voice
  • 3,161
  • 3
  • 34
  • 66
1

requestBody can used to upload

  val body: RequestBody = Builder().setType(MultipartBody.FORM)
        .addFormDataPart(
            "file", "<image name you wish to give>",
            RequestBody.create(
                MediaType.parse("application/octet-stream"),
                File(path)
            )
        )
        .build()
uploadProfilePhoto(body)

then call like this :

   @POST("/**")
    suspend fun uploadProfilePhoto(
        @Body body: RequestBody,
    ): ResponseBody
}
JSONParser
  • 1,112
  • 2
  • 15
  • 30
0

Don't use multiple parameters in the function name just go with simple few args convention that will increase the readability of codes, for this you can do like -

// MultipartBody.Part.createFormData("partName", data)
Call<SomReponse> methodName(@Part MultiPartBody.Part part);
// RequestBody.create(MediaType.get("text/plain"), data)
Call<SomReponse> methodName(@Part(value = "partName") RequestBody part); 
/* for single use or you can use by Part name with Request body */

// add multiple list of part as abstraction |ease of readability|
Call<SomReponse> methodName(@Part List<MultiPartBody.Part> parts); 
Call<SomReponse> methodName(@PartMap Map<String, RequestBody> parts);
// this way you will save the abstraction of multiple parts.

There can be multiple exceptions that you may encounter while using Retrofit, all of the exceptions documented as code, have a walkthrough to retrofit2/RequestFactory.java. you can able to two functions parseParameterAnnotation and parseMethodAnnotation where you can able to exception thrown, please go through this, it will save your much of time than googling/stackoverflow

0

In my case, I needed to send a PDF file (application/pdf), along with JSON information (application/json). Thankfully, Retrofit2 makes this super simple.

My interface looks as follows:

interface MyApi {
    @Multipart
    @POST("upload")
    fun uploadPDF(
        @Part file: MultipartBody.Part,
        @Part(value = "jsoninfo") jsoninfo: MyJsonObject
    ): Call<MyResponse>
}

Where jsoninfo is the name of my JSON data, MyJsonObject is my data class, and MyResponse is the response I'm expecting, of course.

Then, I just call my API method as follows:

val myJsonObject = MyJsonObject(...)

// "file" is of type byte[] already
val requestBody = RequestBody.create(file, MediaType.parse("application/pdf"))
val filePart = MultipartBody.Part.createFormData("file", "myfile.pdf", requestBody)

api.uploadPDF(filePart, myJsonObject).enqueue(...)
Ricardo Yubal
  • 374
  • 3
  • 8