How to get progress for multipart multiple files upload using OkHTTP3/Retrofit2?

Viewed 1996

I am uploading a dynamic number of files in a single multipart request using Retrofit2. My retrofit interface looks like this -

public interface FileUploadService {  
    @Multipart
    @POST("upload")
    Call<ResponseBody> uploadMultipleFilesDynamic(
            @Part List<MultipartBody.Part> files);
}

Now I want to track progress for this multi-file upload. This solution explains how to get progress while uploading a single file in a multipart request by extending RequestBody. Though I can't seem to comprehend how to apply this for my multiple files request. One solution I could think of was to create ProgressRequestBody by extending OkHTTP MultipartBody class instead of RequestBody but OkHTTP3 implements MultipartBody as a final class making it impossible to extend. Can anyone point me in the right direction as it's a huge blocker for me to not be able to show the progress to the user for files upload. Or are there any work arounds that I can implement to achieve this functionality?

1 Answers

I've followed this blogpost: https://medium.com/@PaulinaSadowska/display-progress-of-multipart-request-with-retrofit-and-rxjava-23a4a779e6ba and then made the following adjustments to display the total progression instead of progression of separate files:

  private fun prepareFileParts(reportAttachments: MutableList<ReportAttachment>, emitter: FlowableEmitter<Double>): List<MultipartBody.Part> {
    val multiPartBodyList = mutableListOf<MultipartBody.Part>()

    var offset = 0L
    var totalLength = 0L

    // calculate the total length of all files
    for (attachment in reportAttachments) {
        val file = File(attachment.filePath)
        totalLength += file.length()
    }

    // create requestbody for each file and calculate the progression offset
    for (attachment in reportAttachments) {
        val file = File(attachment.filePath)
        val mimeType = attachment.mimeType

        val countingRequestBody = createCountingRequestBody(file, mimeType, emitter, offset, totalLength)
        offset += file.length()

        val multipartBody = MultipartBody.Part.createFormData("file", file.name, countingRequestBody)
        multiPartBodyList.add(multipartBody)
    }

    return multiPartBodyList
}

private fun createCountingRequestBody(file: File, mimeType: String, emitter: FlowableEmitter<Double>, offset: Long, totalLength: Long): RequestBody {
    val requestBody = RequestBody.create(MediaType.parse(mimeType), file)
    return CountingRequestBody(requestBody, object : CountingRequestBody.Listener {
        override fun onRequestProgress(bytesWritten: Long, contentLength: Long) {
            val progress: Double = 1.0 * (offset + bytesWritten) / totalLength
            emitter.onNext(progress)
        }
    })
}

If you want, you can also create an interceptor and add it to your OkHttpClient. This would track all outgoing API calls by default. It would look something like this:

class UploadProgressInterceptor(private val progressListener: CountingRequestBody.Listener) : Interceptor {

override fun intercept(chain: Interceptor.Chain): Response {
    val originalRequest = chain.request()

    if (originalRequest.body() == null) {
        return chain.proceed(originalRequest)
    }

    val requestBody = originalRequest.body()
    requestBody?.let {
        val progressRequest = originalRequest.newBuilder()
                .method(originalRequest.method(), CountingRequestBody(it, progressListener))
                .build()
        return chain.proceed(progressRequest)
    }
    return chain.proceed(originalRequest)
}
Related