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
- Getting Started and Creating an Android Client
- Basics of API Description
- Creating a Sustainable Android Client
- URL Handling, Resolution and Parsing
- How to Change API Base Url at Runtime
- Multiple Server Environments (Develop, Staging, Production)
- Share OkHttp Client and Converters between Retrofit Instances
- Upgrade Guide from 1.9
- Beyond Android: Retrofit for Java Projects
- How to use OkHttp 3 with Retrofit 1
- Synchronous and Asynchronous Requests
- Send Objects in Request Body
- Add Custom Request Header
- Manage Request Headers in OkHttp Interceptor
- Dynamic Request Headers with @HeaderMap
- Multiple Query Parameters of Same Name
- Optional Query Parameters
- Send Data Form-Urlencoded
- Send Data Form-Urlencoded Using FieldMap
- How to Add Query Parameters to Every Request
- Add Multiple Query Parameter With QueryMap
- How to Use Dynamic Urls for Requests
- Constant, Default and Logic Values for POST and PUT Requests
- Cancel Requests
- Reuse and Analyze Requests
- Optional Path Parameters
- How to Send Plain Text Request Body
- Customize Network Timeouts
- How to Trust Unsafe SSL certificates (Self-signed, Expired)
- Dynamic Endpoint-Dependent Interceptor Actions
- How to Update Objects on the Server (PUT vs. PATCH)
- How to Delete Objects on the Server
- Introduction to (Multiple) Converters
- Adding & Customizing the Gson Converter
- Implementing Custom Converters
- How to Integrate XML Converter
- Access Mapped Objects and Raw Response Payload
- Supporting JSON and XML Responses Concurrently
- Handling of Empty Server Responses with Custom Converter
- Send JSON Requests and Receive XML Responses (or vice versa)
- Unwrapping Envelope Responses with Custom Converter
- Wrapping Requests in Envelope with Custom Converter
- Define a Custom Response Converter
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!