Downloading a file using retrofit

I've started using retrofit recently and it's intuitively simpler than Google's volley. And it's got really good benchmarks. And it's made by the same guys who made Dagger and Picasso. So you should give it a try too if you haven't.

I spent considerable amount of time figuring how to download an image yesterday. So here's how to do it using retrofit. This will work for any file type - not just images.

Create the service interface
public interface IMyService {
    @GET
    @Streaming
    Call<ResponseBody> getFile(@Url String url);
}
Set permissions

The app will require the following permissions:

  1. INTERNET - to access the internet & download the file
  2. WRITE_EXTERNAL_STORAGE - to be able to write to the filesystem & save the file

Add the following code before opening the <application> in the AndroidManifest.xml file:

<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
Create the AsyncTask

Since I'm downloading an image, I'm going to save the file in a folder named after the app in the pictures directory.

private class DownloadFileAsyncTask extends AsyncTask<InputStream, Void, Boolean> {

    final String appDirectoryName = "AppName";
    final File imageRoot = new File(Environment.getExternalStoragePublicDirectory(
            Environment.DIRECTORY_PICTURES), appDirectoryName);
    final String filename = "image.jpg";

    @Override
    protected Boolean doInBackground(InputStream... params) {
        InputStream inputStream = params[0];
        File file = new File(imageRoot, filename); 
        OutputStream output = null;
        try {
            output = new FileOutputStream(file);

            byte[] buffer = new byte[1024]; // or other buffer size
            int read;

            Log.d(TAG, "Attempting to write to: " + imageRoot + "/" + filename);
            while ((read = inputStream.read(buffer)) != -1) {
                output.write(buffer, 0, read);
                Log.v(TAG, "Writing to buffer to output stream.");
            }
            Log.d(TAG, "Flushing output stream.");
            output.flush();
            Log.d(TAG, "Output flushed.");
        } catch (IOException e) {
            Log.e(TAG, "IO Exception: " + e.getMessage());
            e.printStackTrace();
            return false;
        } finally {
            try {
                if (output != null) {
                    output.close();
                    Log.d(TAG, "Output stream closed sucessfully.");
                }
                else{
                    Log.d(TAG, "Output stream is null");
                }
            } catch (IOException e){
                Log.e(TAG, "Couldn't close output stream: " + e.getMessage());
                e.printStackTrace();
                return false;
            }
        }
        return true;
    }

    @Override
    protected void onPostExecute(Boolean result) {
        super.onPostExecute(result);

        Log.d(TAG, "Download success: " + result);
        // TODO: show a snackbar or a toast
    }
}

In my case, the class is an inner class. TAG is defined in the outer class as:

private static final String TAG = "###ActivityName";
Build an instance of the Service
Retrofit retrofit = new Retrofit.Builder()
    .baseUrl("https://api.base.url")
    .build();
IMyService service = retrofit.create(IMyService.class);

The base url is required. The url doesn't matter if you're going to be using an absolute url to download the file. Relative urls must however be relative to the base url.

Initiate the download
service.getFile("https://image.url/goes/here).enqueue(new Callback<ResponseBody>() {
    @Override
    public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
        Log.d(TAG, response.message());
        if(!response.isSuccess()){
            Log.e(TAG, "Something's gone wrong");
            // TODO: show error message
            return;
        }
        DownloadFileAsyncTask downloadFileAsyncTask = new DownloadFileAsyncTask();
        downloadFileAsyncTask.execute(response.body().byteStream());
    }

    @Override
    public void onFailure(Call<ResponseBody> call, Throwable t) {
        Log.e(TAG, t.getMessage());
        // TODO: show error message
    }
});