Retrofit 2 — How to Upload Files to Server

The previous tutorials guided you through various use cases of Retrofit and showed you opportunities to enhance the app with Retrofit’s built-in functionality. This tutorial will show you how to upload a file to a backend server using the second major release of Retrofit, namely Retrofit 2.

Retrofit Series Overview

  1. Ignore Response Payload with Call<Void> (Coming soon)
  1. Simple Error Handling
  2. Error Handling for Synchronous Requests (Coming soon)
  3. Catch Server Errors Globally with Response Interceptor (Coming soon)
  4. How to Detect Network and Conversion Errors in onFailure (Coming soon)
  1. Activate Response Caching (Etag, Last-Modified) (Coming soon)
  2. Check Response Origin (Network, Cache, or Both) (Coming soon)
  3. Force Server Cache Support with Response Interceptor (Coming soon)
  4. Support App Offline Mode by Accessing Response Caches (Coming soon)
  5. Analyze Cache Files (Coming soon)
  1. Callbacks (Coming soon)
  2. Annotations (Coming soon)
  3. Fluent Interface with Builders (Coming soon)

File Upload with Retrofit 1.x

We’ve already published a tutorial on how to upload files using Retrofit 1.x. If you’re using Retrofit 1, please follow the link.

Using Retrofit 2? This tutorial is for you and please read on :)

Upload Files With Retrofit 2

This tutorial is intentionally separated from the already published tutorial on how to upload files with Retrofit v1, because the internal changes from Retrofit 1 to Retrofit 2 are profound and you need to understand the way Retrofit 2 handles file uploads.

Before we dive deeper into the file upload topic with Retrofit 2, let’s shortly recap the previously used functionality in v1. Retrofit 1 used a class called TypedFile for file uploads to a server. This class has been removed from Retrofit 2. Further, Retrofit 2 now leverages the OkHttp library for any network operation and, as a result, OkHttp’s classes for use cases like file uploads.

Using Retrofit 2, you need to use either OkHttp’s RequestBody or MultipartBody.Part classes and encapsulate your file into a request body. Let’s have a look at the interface definition for file uploads.

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

Let me explain each part of the definition above. First, you need to declare the entire call as @Multipart request. Let's continue with the annotation for description. The description is just a string value wrapped within a RequestBody instance. Secondly, there’s another @Part within the request: the actual file. We use the MultipartBody.Part class that allows us to send the actual file name besides the binary file data with the request. You’ll see how to create the file object correctly within the following section.

Android Client Code

At this point, you’ve defined the necessary service interface for Retrofit. Now you can move on and touch the actual file upload. We’ll use the ServiceGenerator class which generates a service client. We’ve introduced the ServiceGenerator class in the creating a sustainable Android client tutorial earlier in this series.

The following code snippet shows the uploadFile(Uri fileUri) method taking the file’s uri as a parameter. If you’re starting an intent to choose a file, you’ll return within the onActivityResult() method of Android’s lifecycle. In this method, you can get the file’s uri and that’s exactly what you’ll use to upload the file within the uploadFile method.

private void uploadFile(Uri fileUri) {  
    // create upload service client
    FileUploadService service =
            ServiceGenerator.createService(FileUploadService.class);

    // 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
    MultipartBody.Part body =
            MultipartBody.Part.createFormData("picture", file.getName(), requestFile);

    // add another part within the multipart request
    String descriptionString = "hello, this is description speaking";
    RequestBody description =
            RequestBody.create(
                    okhttp3.MultipartBody.FORM, descriptionString);

    // finally, execute the request
    Call<ResponseBody> call = service.upload(description, body);
    call.enqueue(new Callback<ResponseBody>() {
        @Override
        public void onResponse(Call<ResponseBody> call,
                               Response<ResponseBody> response) {
            Log.v("Upload", "success");
        }

        @Override
        public void onFailure(Call<ResponseBody> call, Throwable t) {
            Log.e("Upload error:", t.getMessage());
        }
    });
}

The snippet above shows you the code to initialize the payload (body and description) and how to use the file upload service. As already mentioned, the RequestBody class is from OkHttp and used for the description. Its .create() method requires two parameters: first, the media type, and second, the actual data. The media type for the description can simply be OkHttp's constant for multipart requests: okhttp3.MultipartBody.FORM. The media type for the file should ideally be the actual content-type. For example, a PNG image should have image/png. Our code snippet above figures out the content type with the getContentResolver().getType(fileUri) call and then parses the result into OkHttp's media type with MediaType.parse().

Besides the description, you’ll add the file wrapped into a MultipartBody.Part instance. That’s what you need to use to appropriately upload files from client-side. Further, you can add the original file name within the createFormData() method and reuse it on your backend.

Remember the Content-Type

Please keep an eye on Retrofit’s content type. If you intercept the underlying OkHttp client and change the content type to application/json, your server might have issues with the deserialization process. Make sure you’re not defining the header indicating you’re sending JSON data, but multipart/form-data.

Next: Upload Files with Progress Updates

If you upload files in the foreground and they are not small, you might want to inform the user on your actions. Ideally, you would display progress updates how much you've uploaded already. We've another tutorial on how to upload files with progress updates.

Exemplary Hapi Server for File Uploads

If you already have your backend project, you can lean on the example code below. We use a simple hapi server with a POST route available at /upload. Additionally, we tell hapi to don’t parse the incoming request, because we use a Node.js library called multiparty for the payload parsing.

Within the callback of multiparty’s parsing function, we’re logging each field to show its output.

method: 'POST',  
path: '/upload',  
config: {  
    payload: {
        maxBytes: 209715200,
        output: 'stream',
        parse: false
    },
    handler: function(request, reply) {
        var multiparty = require('multiparty');
        var form = new multiparty.Form();
        form.parse(request.payload, function(err, fields, files) {
            console.log(err);
            console.log(fields);
            console.log(files);

            return reply(util.inspect({fields: fields, files: files}));
        });
    }
}

Android client expects a return type of String, we’re sending the received information as response. Of course your response will and should look different :)

Below you can see the output of a successful request and payload parsing on server-side. The first null is the err object. Afterwards, you can see the fields which is only the description as part of the request. And last but not least, the file is available within the picture field. Here you see our previously defined names on client side. 20160312_095248.jpg is passed as the original name and the actual field name is picture. For further processing, access the uploaded image at path’s location.

Server Log for Parsed Payload

null  
{ description: [ 'hello, this is description speaking' ] }
{ picture:
   [ { fieldName: 'picture',
       originalFilename: '20160312_095248.jpg',
       path: '/var/folders/rq/q_m4_21j3lqf1lw48fqttx_80000gn/T/X_sxX6LDUMBcuUcUGDMBKc2T.jpg',
       headers: [Object],
       size: 39369 } ] }

Outlook

File uploads are an essential feature within up-to-date apps and you can integrate this feature within your app using Retrofit. This tutorial guided you through the necessary steps to upload a file from your Android device to your backend server.

What to expect within the next post on Retrofit? Next week you’ll learn all about how to get back logging within Retrofit 2. Stay tuned, it will be a good shot!


Explore the Library

Find interesting tutorials and solutions for your problems.

Miscellaneous