If you use multipart for upload:
Create a new class extending RequestBody
:
public class ProgressRequestBody extends RequestBody {
private static final int DEFAULT_BUFFER_SIZE = 2048;
private UploadProgressListener listener;
private File file;
private String path;
private String content_type;
public interface UploadProgressListener {
void onProgressUpdate(int percentage);
}
public ProgressRequestBody(final File file, String contentType, final UploadProgressListener listener) {
this.file = file;
this.listener = listener;
this.content_type = contentType;
}
@Override
public MediaType contentType() {
return MediaType.parse(content_type+"/*");
}
@Override
public long contentLength() throws IOException {
return file.length();
}
@Override
public void writeTo(BufferedSink sink) throws IOException {
long fileLength = file.length();
byte[] buffer = new byte[DEFAULT_BUFFER_SIZE];
FileInputStream in = new FileInputStream(file);
long uploaded = 0;
try {
int read;
Handler handler = new Handler(Looper.getMainLooper());
while ((read = in.read(buffer)) != -1) {
// update progress on UI thread
handler.post(new ProgressUpdater(uploaded, fileLength));
uploaded += read;
sink.write(buffer, 0, read);
}
} finally {
in.close();
}
}
private class ProgressUpdater implements Runnable {
private long mUploaded;
private long mTotal;
public ProgressUpdater(long uploaded, long total) {
mUploaded = uploaded;
mTotal = total;
}
@Override
public void run() {
listener.onProgressUpdate((int)(100 * mUploaded / mTotal));
}
}
}
Then create your api interface:
@Multipart
@POST("/your_upload_url")
Call<YourJsonObject> uploadImage(@Part MultipartBody.Part file);
Use it like this:
ProgressRequestBody fileBody = new ProgressRequestBody(file, "your content type", new ProgressRequestBody.UploadProgressListener() {
@Override
public void onProgressUpdate(int percentage) {
// Update the progressbar
}
}););
MultipartBody.Part filePart =
MultipartBody.Part.createFormData("image", file.getName(), fileBody);
Call<JsonObject> request = RetrofitClient.uploadImage(filepart);
request.enqueue(new Callback<JsonObject>() {
@Override
public void onResponse(Call<JsonObject> call, Response<JsonObject> response) {
if(response.isSuccessful()){
//Do something
}
}
@Override
public void onFailure(Call<JsonObject> call, Throwable t) {
}
});
}
Done.
Alternate method using interceptor:
We can add an interceptor to the okhttp client to track the upload progress.
First, you need to extend okhttp's ResponseBody
class like this:
import java.io.IOException;
import okhttp3.MediaType;
import okhttp3.ResponseBody;
import okio.Buffer;
import okio.BufferedSource;
import okio.ForwardingSource;
import okio.Okio;
import okio.Source;
public class ProgressResponseBody extends ResponseBody {
private final ResponseBody responseBody;
private final ProgressListener progressListener;
private BufferedSource bufferedSource;
ProgressResponseBody(ResponseBody responseBody, ProgressListener progressListener) {
this.responseBody = responseBody;
this.progressListener = progressListener;
}
@Override
public MediaType contentType() {
return responseBody.contentType();
}
@Override
public long contentLength() {
return responseBody.contentLength();
}
@Override
public BufferedSource source() {
if (bufferedSource == null) {
bufferedSource = Okio.buffer(source(responseBody.source()));
}
return bufferedSource;
}
private Source source(Source source) {
return new ForwardingSource(source) {
long totalBytesRead = 0L;
@Override
public long read(Buffer sink, long byteCount) throws IOException {
long bytesRead = super.read(sink, byteCount);
// read() returns the number of bytes read, or -1 if this source is exhausted.
totalBytesRead += bytesRead != -1 ? bytesRead : 0;
progressListener.update(totalBytesRead, responseBody.contentLength(), bytesRead == -1);
return bytesRead;
}
};
}
}
interface ProgressListener {
void update(long bytesRead, long contentLength, boolean done);
}
This class is responsible for tracking the progress and updating the listener.
Then you must create a new instance of listener:
final ProgressListener progressListener = new ProgressListener() {
boolean firstUpdate = true;
@Override public void update(long bytesRead, long contentLength, boolean done) {
if (done) {
System.out.println("completed");
} else {
if (firstUpdate) {
firstUpdate = false;
if (contentLength == -1) {
System.out.println("content-length: unknown");
} else {
System.out.format("content-length: %d\n", contentLength);
}
}
System.out.println(bytesRead);
if (contentLength != -1) {
System.out.format("%d%% done\n", (100 * bytesRead) / contentLength);
}
}
}
};
Then you must add an interceptor to your okhttp client:
OkHttpClient okHttpClient = new OkHttpClient.Builder()
.addNetworkInterceptor(chain -> {
Response originalResponse = chain.proceed(chain.request());
return originalResponse.newBuilder()
.body(new ProgressResponseBody(originalResponse.body(), progressListener))
.build();
})
.build();
Finally, add the new client to your retrofit:
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("some url")
.client(okHttpClient)
.build();
Please note that this method tracks all API calls, not just a specific API for upload, to solve this problem, you can pass the request URL to ProgressListener.update()
then you can use EventBus to post an event containing the URL and the progress. Feel free to ask if you have any questions about this last part.
P.S. This method is based on official okhttp samples.