In this tutorial, we’ll create an android application which downloads a file from the URL using Retrofit.
To know the basics of Retrofit, visit this tutorial.
Android Retrofit Download File
We can create a retrofit call in the following way in order to download file:
1 2 3 4 |
@GET Call<ResponseBody> downloadFileWithc(@Url String urlString); |
We can pass the URL of the file we want to download. If we are downloading a file present in the resources we can do this:
1 2 3 4 |
@GET("/resource/path_to_file_with_extension") Call<ResponseBody> downloadFileStatic(); |
It’s recommended to use a @Streaming
annotation on top of the @GET for downloading files. Otherwise Retrofit would move the entire file into memory. Using @Streaming the bytes would be accessed currently without eating up the memory.
When using @Streaming
you must add the code that writes the downloaded data, into a separate thread.
Using the enqueue method we can start the request.
Inside it, we need to create an AsyncTask or use RxJava. We’ll go with the former in this tutorial.
In the following android application that we are going to build, we’ll show the file download progress on a ProgressBar.
Project Setup
Add the following dependencies to your app’s build.gradle:
1 2 3 4 |
implementation 'com.squareup.retrofit2:retrofit:2.3.0' implementation 'com.squareup.okhttp3:okhttps:3.10.0' |
Add the following permissions in your Manifest:
1 2 3 4 |
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.INTERNET" /> |
Following is how our Project Structure looks:
Code
The code for the activity_main.xml
layout is given below:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
<?xml version="1.0" encoding="utf-8"?> <android.support.constraint.ConstraintLayout xmlns:android="https://schemas.android.com/apk/res/android" xmlns:app="https://schemas.android.com/apk/res-auto" xmlns:tools="https://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity"> <TextView android:id="@+id/txtProgressPercent" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Downloaded 0%" android:textColor="@color/colorAccent" android:textStyle="bold" android:textAppearance="@style/Base.TextAppearance.AppCompat.Display1" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toTopOf="parent" /> <ProgressBar android:id="@+id/progressBar" style="?android:attr/progressBarStyleHorizontal" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginLeft="16dp" android:layout_marginRight="16dp" android:layout_marginTop="16dp" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/txtProgressPercent" /> <Button android:id="@+id/button" style="@style/ButtonStyle" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="16dp" android:text="DOWNLOAD FILE" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/progressBar" /> </android.support.constraint.ConstraintLayout> |
We’ve set a style on the button in the styles.xml.
The code for the RetrofitInterface.java class is given below:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
package com.journaldev.androidretrofitdownloadfileprogress; import okhttp3.ResponseBody; import retrofit2.Call; import retrofit2.http.GET; import retrofit2.http.Streaming; import retrofit2.http.Url; public interface RetrofitInterface { @Streaming @GET Call<ResponseBody> downloadFileByUrl(@Url String fileUrl); } |
The code for the MainActivity.java class is given below:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 |
package com.journaldev.androidretrofitdownloadfileprogress; import android.Manifest; import android.content.pm.PackageManager; import android.os.AsyncTask; import android.os.Environment; import android.support.v4.app.ActivityCompat; import android.support.v4.content.ContextCompat; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.util.Log; import android.util.Pair; import android.view.View; import android.widget.Button; import android.widget.ProgressBar; import android.widget.TextView; import android.widget.Toast; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import okhttp3.OkHttpClient; import okhttp3.ResponseBody; import retrofit2.Call; import retrofit2.Callback; import retrofit2.Response; import retrofit2.Retrofit; public class MainActivity extends AppCompatActivity { TextView txtProgressPercent; ProgressBar progressBar; Button btnDownloadFile; DownloadZipFileTask do<img class="alignnone size-medium wp-image-33989" src="https://all-learning.com/wp-content/uploads/2018/08/android-retrofit-file-download-progress-12-300x157.png" alt="android-retrofit-file-download-progress (1)" width="300" height="157" />wnloadZipFileTask; private static final String TAG = "MainActivity"; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); askForPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE, 101); txtProgressPercent = findViewById(R.id.txtProgressPercent); progressBar = findViewById(R.id.progressBar); btnDownloadFile = findViewById(R.id.button); btnDownloadFile.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { downloadZipFile(); } }); } private void downloadZipFile() { RetrofitInterface downloadService = createService(RetrofitInterface.class, "https://github.com/"); Call<ResponseBody> call = downloadService.downloadFileByUrl("anupamchugh/AnimateTextAndImageView/archive/master.zip"); call.enqueue(new Callback<ResponseBody>() { @Override public void onResponse(Call<ResponseBody> call, final Response<ResponseBody> response) { if (response.isSuccessful()) { Log.d(TAG, "Got the body for the file"); Toast.makeText(getApplicationContext(), "Downloading...", Toast.LENGTH_SHORT).show(); downloadZipFileTask = new DownloadZipFileTask(); downloadZipFileTask.execute(response.body()); } else { Log.d(TAG, "Connection failed " + response.errorBody()); } } @Override public void onFailure(Call<ResponseBody> call, Throwable t) { t.printStackTrace(); Log.e(TAG, t.getMessage()); } }); } public <T> T createService(Class<T> serviceClass, String baseUrl) { Retrofit retrofit = new Retrofit.Builder() .baseUrl(baseUrl) .client(new OkHttpClient.Builder().build()) .build(); return retrofit.create(serviceClass); } private class DownloadZipFileTask extends AsyncTask<ResponseBody, Pair<Integer, Long>, String> { @Override protected void onPreExecute() { super.onPreExecute(); } @Override protected String doInBackground(ResponseBody... urls) { //Copy you logic to calculate progress and call saveToDisk(urls[0], "journaldev-project.zip"); return null; } protected void onProgressUpdate(Pair<Integer, Long>... progress) { Log.d("API123", progress[0].second + " "); if (progress[0].first == 100) Toast.makeText(getApplicationContext(), "File downloaded successfully", Toast.LENGTH_SHORT).show(); if (progress[0].second > 0) { int currentProgress = (int) ((double) progress[0].first / (double) progress[0].second * 100); progressBar.setProgress(currentProgress); txtProgressPercent.setText("Progress " + currentProgress + "%"); } if (progress[0].first == -1) { Toast.makeText(getApplicationContext(), "Download failed", Toast.LENGTH_SHORT).show(); } } public void doProgress(Pair<Integer, Long> progressDetails) { publishProgress(progressDetails); } @Override protected void onPostExecute(String result) { } } private void saveToDisk(ResponseBody body, String filename) { try { File destinationFile = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS), filename); InputStream inputStream = null; OutputStream outputStream = null; try { inputStream = body.byteStream(); outputStream = new FileOutputStream(destinationFile); byte data[] = new byte[4096]; int count; int progress = 0; long fileSize = body.contentLength(); Log.d(TAG, "File Size=" + fileSize); while ((count = inputStream.read(data)) != -1) { outputStream.write(data, 0, count); progress += count; Pair<Integer, Long> pairs = new Pair<>(progress, fileSize); downloadZipFileTask.doProgress(pairs); Log.d(TAG, "Progress: " + progress + "/" + fileSize + " >>>> " + (float) progress / fileSize); } outputStream.flush(); Log.d(TAG, destinationFile.getParent()); Pair<Integer, Long> pairs = new Pair<>(100, 100L); downloadZipFileTask.doProgress(pairs); return; } catch (IOException e) { e.printStackTrace(); Pair<Integer, Long> pairs = new Pair<>(-1, Long.valueOf(-1)); downloadZipFileTask.doProgress(pairs); Log.d(TAG, "Failed to save the file!"); return; } finally { if (inputStream != null) inputStream.close(); if (outputStream != null) outputStream.close(); } } catch (IOException e) { e.printStackTrace(); Log.d(TAG, "Failed to save the file!"); return; } } private void askForPermission(String permission, Integer requestCode) { if (ContextCompat.checkSelfPermission(MainActivity.this, permission) != PackageManager.PERMISSION_GRANTED) { if (ActivityCompat.shouldShowRequestPermissionRationale(MainActivity.this, permission)) { ActivityCompat.requestPermissions(MainActivity.this, new String[]{permission}, requestCode); } else { ActivityCompat.requestPermissions(MainActivity.this, new String[]{permission}, requestCode); } } else if (ContextCompat.checkSelfPermission(MainActivity.this, permission) == PackageManager.PERMISSION_DENIED) { Toast.makeText(getApplicationContext(), "Permission was denied", Toast.LENGTH_SHORT).show(); } } @Override public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) { super.onRequestPermissionsResult(requestCode, permissions, grantResults); if (ActivityCompat.checkSelfPermission(this, permissions[0]) == PackageManager.PERMISSION_GRANTED) { if (requestCode == 101) Toast.makeText(this, "Permission granted", Toast.LENGTH_SHORT).show(); } else { Toast.makeText(this, "Permission denied", Toast.LENGTH_SHORT).show(); } } } |
In the above code, we are downloading a GitHub repository zip file.
We do the following list of things in the above code:
- Runtime Permissions – We need this for saving the file in our phone storage.
- Building a Retrofit Service using OkHttp
- Downloading the file from the url in the Async by using
response.body()
. - Inside the AsyncTask, we create a public method
doProgress
in which we invoke the AsyncTask method publishProgress(). - publishProgress triggers the
onProgressUpdate()
method of AsyncTask from the doInBackground. - Doing so we can determine the progress of the file download and update it on the ProgressBar.
- The downloaded file path is set inside the downloads folder in the Internal Storage.
The output of the application in action is given below:
And a screenshot from our File manager proves that the file is downloaded:
This brings an end to this tutorial. You can download the project from the link below: