In this blog post of our Retrofit launch sequence we'll show you one of the most requested topics: how to download files. We'll give you all the insight and snippets you need to use Retrofit to download everything, from tiny .png
's to large .zip
files.
If this is your first Retrofit post on futurestud.io, feel free to browse our other Retrofit posts:
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
How to Specify the Retrofit Request
If you're reading this and you haven't written code for any Retrofit requests yet, please check our previous blog posts to get started. For all you Retrofit experts: the request declaration for downloading files looks almost like any other request:
// option 1: a resource relative to your base URL
@GET("/resource/example.zip")
Call<ResponseBody> downloadFileWithFixedUrl();
// option 2: using a dynamic URL
@GET
Call<ResponseBody> downloadFileWithDynamicUrlSync(@Url String fileUrl);
If the file you want to download is a static resource (always at the same spot on the server) and on the server your base URL refers to, you can use option 1. As you can see, it looks like a regular Retrofit 2 request declaration. Please note, that we're specifying ResponseBody
as return type. You should not use anything else here, otherwise Retrofit will try to parse and convert it, which doesn't make sense when you're downloading a file.
The second option is new to Retrofit 2. You can now easily pass a dynamic value as full URL to the request call. This can be especially helpful when downloading files, which are dependent of a parameter, user or time. You can build the URL during runtime and request the exact file without any hacks. If you haven't worked with dynamic URLs yet, feel free to head over to our blog post for that topic: dynamic urls in Retrofit 2
Pick what kind of option is useful to you and move on to the next section.
How to Call the Request
After declaring our request, we need to actually call it:
FileDownloadService downloadService = ServiceGenerator.create(FileDownloadService.class);
Call<ResponseBody> call = downloadService.downloadFileWithDynamicUrlSync(fileUrl);
call.enqueue(new Callback<ResponseBody>() {
@Override
public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
if (response.isSuccess()) {
Log.d(TAG, "server contacted and has file");
boolean writtenToDisk = writeResponseBodyToDisk(response.body());
Log.d(TAG, "file download was a success? " + writtenToDisk);
} else {
Log.d(TAG, "server contact failed");
}
}
@Override
public void onFailure(Call<ResponseBody> call, Throwable t) {
Log.e(TAG, "error");
}
});
If you're confused by the ServiceGenerator.create()
, head over to our first blog post to get started. Once we've created the service, we'll make the request just like any other Retrofit call!
There is just one thing left, which currently hides behind the function writeResponseBodyToDisk()
: writing the file to the disk!
How to Save the File
The writeResponseBodyToDisk()
method takes the ResponseBody
object and reads and writes the byte values of it to the disk. The code looks much more difficult than it actually is:
private boolean writeResponseBodyToDisk(ResponseBody body) {
try {
// todo change the file location/name according to your needs
File futureStudioIconFile = new File(getExternalFilesDir(null) + File.separator + "Future Studio Icon.png");
InputStream inputStream = null;
OutputStream outputStream = null;
try {
byte[] fileReader = new byte[4096];
long fileSize = body.contentLength();
long fileSizeDownloaded = 0;
inputStream = body.byteStream();
outputStream = new FileOutputStream(futureStudioIconFile);
while (true) {
int read = inputStream.read(fileReader);
if (read == -1) {
break;
}
outputStream.write(fileReader, 0, read);
fileSizeDownloaded += read;
Log.d(TAG, "file download: " + fileSizeDownloaded + " of " + fileSize);
}
outputStream.flush();
return true;
} catch (IOException e) {
return false;
} finally {
if (inputStream != null) {
inputStream.close();
}
if (outputStream != null) {
outputStream.close();
}
}
} catch (IOException e) {
return false;
}
}
Most of it is just regular Java I/O boilerplate. You might need to adjust the first line on where and with what name your file is being saved. When you have done that, you're ready to download files with Retrofit!
But we're not completely ready for all files yet. There is one major issue: by default, Retrofit puts the entire server response into memory before processing the result. This works fine for some JSON or XML responses, but large files can easily cause Out-of-Memory-Errors.
If your app needs to download even slightly larger files, we strongly recommend reading the next section.
Beware with Large Files: Use @Streaming!
If you’re downloading a large file, Retrofit would try to move the entire file into memory. In order to avoid that, we've to add a special annotation to the request declaration:
@Streaming
@GET
Call<ResponseBody> downloadFileWithDynamicUrlAsync(@Url String fileUrl);
The @Streaming
declaration doesn't mean you're watching a Netflix file. It means that instead of moving the entire file into memory, it'll pass along the bytes right away. But be careful, if you're adding the @Streaming
declaration and continue to use the code above, Android will trigger a android.os.NetworkOnMainThreadException
.
Thus, the final step is to wrap the call into a separate thread, for example with a lovely ASyncTask:
final FileDownloadService downloadService =
ServiceGenerator.createService(FileDownloadService.class);
Call<ResponseBody> call = downloadService.downloadFileWithDynamicUrlSync(fileUrl);
call.enqueue(new Callback<ResponseBody>() {
@Override
public void onResponse(Call<ResponseBody> call, final Response<ResponseBody> response) {
if (response.isSuccessful()) {
Log.d(TAG, "server contacted and has file");
new AsyncTask<Void, Void, Void>() {
@Override
protected Void doInBackground(Void... voids) {
boolean writtenToDisk = writeResponseBodyToDisk(FileDownloadActivity.this, response.body(), null);
Log.d(TAG, "file download was a success? " + writtenToDisk);
return null;
}
}.execute();
}
else {
Log.d(TAG, "server contact failed");
}
}
@Override
public void onFailure(Call<ResponseBody> call, Throwable t) {
Log.e(TAG, "error");
}
});
You can still use the same writeResponseBodyToDisk()
method. If you remember the @Streaming
declaration and this snippet, you can download even large files with Retrofit efficiently.
Next: Download Files with Progress Updates
If you download 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 downloaded already. We've another tutorial on how to download files with progress updates.
Summary
In this tutorial you've seen how to download files with Retrofit efficiently. This is everything you need to know to download files with Retrofit.
If you've any questions, let us know in the comments or on twitter.