For uploading a file to a server, in Android, we can make use of Retrofit library. Retrofit
uses OkHttp
for Http requests, which in turn provides us with the Multipart support. We use Multipart to upload as it is helpful in uploading large files because it uploads a single in multiple parts, hence increasing the efficiency of upload success.
Firstly, we will set up our request interface, IImageUpload
and also a service class, ImageUploadService
which will provide us with a Retrofit
instance for our activity.
public interface IImageUpload {
@Multipart
@POST("upload")
Call<Void> uploadImage(@Part MultipartBody.Part file);
}
public final class ImageUploadService {
public static IImageUpload getInstance(Context ctx) {
return RetrofitClient.getInstance(ctx).create(IImageUpload.class);
}
}
We are assuming here that you have the path of the file to be uploaded with you. Note here that we will be using enqueue
function , which will execute the request asynchronously.
File file = new File(filePath);
RequestBody fileBody = new RequestBody(MediaType.parse("image/*"), file);
/* Notice here the first argument in the createFormData function is the name of the key whose value will be the file you send */
MultipartBody.Part filePart = MultipartBody.Part.createFormData("file",file.getName(),fileBody);
Call<Void> uploadImage = ImageUploadService.getInstance(this).uploadImage(filePart);
uploadImage.enqueue(new Callback<Void>() {
@Override
public void onResponse(@NonNull Call<Void> call, @NonNull Response<Void> response) {
this.onSuccess("Uploaded successfully");
}
@Override
public void onFailure(@NonNull Call<Void> call, @NonNull Throwable t) {
this.onError("Failed to upload image");
}
});
Now we will add a progress bar to show upload progress. OkHttp3
, which has a subclass RequestBody
can be extended and modified for our use. This class contains a function writeTo
which we need to override to get the file upload progress.
public class ProgressRequestBody extends RequestBody {
private File mFile;
private String mPath;
private String content_type;
private String upload;
private static final int DEFAULT_BUFFER_SIZE = 2048;
public ProgressRequestBody(final File file, String content_type) {
this.content_type = content_type;
mFile = file;
}
@Nullable
@Override
public MediaType contentType() {
return MediaType.parse(content_type+"/*");
}
@Override
public long contentLength() throws IOException {
return mFile.length();
}
@Override
public void writeTo(@NonNull BufferedSink sink) throws IOException {
long fileLength = mFile.length();
byte[] buffer = new byte[DEFAULT_BUFFER_SIZE];
long uploaded = 0;
try (FileInputStream in = new FileInputStream(mFile)) {
int read;
Handler handler = new Handler(Looper.getMainLooper());
while ((read = in.read(buffer)) != -1) {
uploaded += read;
sink.write(buffer, 0, read);
handler.post(new ProgressUpdater(uploaded, fileLength));
}
}
}
private class ProgressUpdater implements Runnable {
private long mUploaded;
private long mTotal;
ProgressUpdater(long uploaded, long total) {
mUploaded = uploaded;
mTotal = total;
}
@Override
public void run() {
//update progress here in the UI
}
}
}
Now, to update UI regarding the progress we will be using a callback, this will also ensure separation of concerns.
public interface ImageUploadCallback {
void onProgressUpdate(int percentage);
void onError(String message);
void onSuccess(String message);
}
Now , we will send this callback to the ProgressRequestBody
class from our activity. This will result in some changes in the class.
public class ProgressRequestBody extends RequestBody {
private File mFile;
private String mPath;
private ImageUploadCallback mListener;
private String content_type;
private String upload;
private static final int DEFAULT_BUFFER_SIZE = 2048;
public ProgressRequestBody(final File file, String content_type, final ImageUploadCallback listener) {
this.content_type = content_type;
mFile = file;
mListener = listener; //callback passed from the activity
}
@Nullable
@Override
public MediaType contentType() {
return MediaType.parse(content_type+"/*");
}
@Override
public long contentLength() throws IOException {
return mFile.length();
}
@Override
public void writeTo(@NonNull BufferedSink sink) throws IOException {
long fileLength = mFile.length();
byte[] buffer = new byte[DEFAULT_BUFFER_SIZE];
long uploaded = 0;
try (FileInputStream in = new FileInputStream(mFile)) {
int read;
Handler handler = new Handler(Looper.getMainLooper());
while ((read = in.read(buffer)) != -1) {
uploaded += read;
sink.write(buffer, 0, read);
handler.post(new ProgressUpdater(uploaded, fileLength));
}
}
}
private class ProgressUpdater implements Runnable {
private long mUploaded;
private long mTotal;
ProgressUpdater(long uploaded, long total) {
mUploaded = uploaded;
mTotal = total;
}
@Override
public void run() {
if(mListener!=null)
mListener.onProgressUpdate((int)(100 * mUploaded / mTotal)); //updating the UI of the progress
}
}
}
The only thing remaining is, the request for image upload. We will use Call
for making the request asynchronously. We assume you have the path of the file to be uploaded.
// this activity implements the ImageUploadCallback
File file = new File(filePath);
ProgressRequestBody fileBody = new ProgressRequestBody(file, "image",this);
/* Notice here the first argument in the createFormData function is the name of the key whose value will be the file you send */
MultipartBody.Part filePart = MultipartBody.Part.createFormData("file",file.getName(),fileBody);
Call<Void> uploadImage = ImageUploadService.getInstance(this).uploadImage(filePart);
uploadImage.enqueue(new Callback<Void>() {
@Override
public void onResponse(@NonNull Call<Void> call, @NonNull Response<Void> response) {
this.onSuccess("Uploaded successfully");
}
@Override
public void onFailure(@NonNull Call<Void> call, @NonNull Throwable t) {
this.onError("Failed to upload image");
}
});
@Override
public void onProgressUpdate(int percentage) {
// set current progress
progressBar.setProgress(percentage);
}
@Override
public void onError(String message) {
// do something on error
}
@Override
public void onFinish(String message) {
// do something on upload finished
//for example, start next uploading at the queue
}
Now there’s a catch in this method. If you are using HttpLoggingInterceptor
in your Retrofit
client, then this interceptor will call the writeTo
function for logging purposes before the actual upload takes place. Therefore, when writeTo
function is called second time then actual upload starts, hence we will keep a check and will update progress only on the second run of the writeTo
function.
public class ProgressRequestBody extends RequestBody {
private File mFile;
private String mPath;
private ImageUploadCallback mListener;
private String content_type;
private String upload;
// checks when the function is called second time
private int writeToCall = 0;
private static final int DEFAULT_BUFFER_SIZE = 2048;
public ProgressRequestBody(final File file, String content_type, final ImageUploadCallback listener) {
this.content_type = content_type;
mFile = file;
mListener = listener;
this.writeToCall = 0;
}
@Nullable
@Override
public MediaType contentType() {
return MediaType.parse(content_type+"/*");
}
@Override
public long contentLength() throws IOException {
return mFile.length();
}
@Override
public void writeTo(@NonNull BufferedSink sink) throws IOException {
writeToCall++; // update the counter
long fileLength = mFile.length();
byte[] buffer = new byte[DEFAULT_BUFFER_SIZE];
long uploaded = 0;
try (FileInputStream in = new FileInputStream(mFile)) {
int read;
Handler handler = new Handler(Looper.getMainLooper());
while ((read = in.read(buffer)) != -1) {
uploaded += read;
sink.write(buffer, 0, read);
if (writeToCall == 2) { // updating the progress
handler.post(new ProgressUpdater(uploaded, fileLength));
}
}
}
}
private class ProgressUpdater implements Runnable {
private long mUploaded;
private long mTotal;
ProgressUpdater(long uploaded, long total) {
mUploaded = uploaded;
mTotal = total;
}
@Override
public void run() {
if(mListener!=null)
mListener.onProgressUpdate((int)(100 * mUploaded / mTotal));
}
}
}
There you go! You have successfully uploaded your file while showing the progress. Happy Coding!!