Display progress of multipart request with Retrofit and RxJava
To create an app with great user experience, showing a progress of long-running operations is very important. When you’re sending a file to the server you might wanna show the user how much of the file has already been processed so he can estimate how much longer he’ll have to wait for the upload to finish.
Unfortunately Retrofit doesn’t provide this functionality out of the box. When you’re sending a file using multipart request you will only receive one response from the server without any steps in the middle with the information about upload progress.
OkHttp Recipe
When I researched this problem I came across the solution suggested in OkHttp repository.
In the recipe, they use network interceptor which wraps original response and creates the new one with progress listener. Whats important, interceptor intercepts requests from all endpoints exposed by the service.
To use the solution you have to create ProgressResponseBody
class which extends ResponseBody
and gets progress listener in the constructor. In the writeTo
method, the listener’s update
method is called with totalBytesRead
amount and responseBody.contentLength()
. Having this information we can simply calculate and display progress.
At the first glance, this solution might seem ok. But for me, it has some serious drawbacks which disqualify it from using in well-designed projects.
Drawbacks:
1When you create the service you usually have many endpoints exposed by single API. In most of them you won’t need to track the progress and even if you do, you’ll probably need to separate listeners for each endpoint.
2 Another problem is that when you use Dagger to create and inject dependencies passing the listener to the interceptor of OkHttpClient
may be hard and inconsistent with app architecture.
In my opinion, there is a definitely better solution to this problem which allows to track a progress of only selected requests and ensures to preserve a better separation of concerns.
Alternative solution
Firstly you have to create multipart request method as you will do normally when you want to upload a file without tracking progress:
Typically we would call this method and pass an image of type MultipartBody.Part
to it. MultipartBody.Part
instance is created in the following way:
- Create a new file from
filePath
. - Add file to
RequestBody
with a suitable media type. - Create
MultipartBody.Part
with form data name, file name andRequestBody
.
But to track the file upload progress we will need some additional steps.
Returning progress
Firstly we have to create new Flowable<Double>
which will return progress in the onNext
method, and call onComplete
when the result is received. I used Flowable.create
method with BackpressureStrategy.LATEST
. It keeps only the latest onNext
value, overwriting any previous value if the downstream can’t keep up. I did that because it will not hurt us if some of the progress steps will be omitted if the downstream can’t keep up.
Of course, createMultipartBody
method changed. Now emitter of a type FlowableEmitter<Double>
is passed to it so theonNext
method can be called when upload progress changed. To track file upload I used slightly modified CountingRequestBody class from OkHttpUtils library.
This class is very similar to ProgressResponseBody
shown in OkHttp recipe. It extends RequestBody
class and wraps regular request body functionality adding progress listener.
So now, using CountingRequestBody
we can create MultipartBody.Part
instance which allows tracking upload progress similar as before:
- Create a new file from
filePath
. ← the same - Add file to
RequestBody
with a suitable media type. ← the same - Wrap RequestBody into
CountingRequestBody
. Create and pass listener to it and callemitter.onNext(progress)
when progress changed. ← new step - Create
MultipartBody.Part
with form data name, file name andRequestBody
. ← the same
I think this solution is much more flexible than recipe provided by OkHttp. It allows you to track progress of only selected requests and doesn’t force you to provide progress listener implementation on the creation of OkHttpClient
.
Be careful when disposing!
Please note that in the uploadImage
method when exception thrown by postImage
method is caught I called emitter.tryOnError
instead of emitter.onError
. It’s very important, because otherwise when you’ll try to dispose disposable during upload, you’ll receive io.reactivex.exceptions.UndeliverableException. It happens because disposing cause error in wrapped single and the error cannot be delivered to the flowable which already has been disposed. The tryOnError
method allows solving this issue, because unlike onError
it doesn't call RxJavaPlugins.onError
if the error could not be delivered.
Sidenotes:
- You may notice that when creating
Flowable<Double>
fromSingle<ResponseBody>
I ignored response from the server. If you need the data from the response you can makeuploadImage
method returnFlowable<SomeResponseClass>
instead ofDouble
and store both progress and server’s response in the object. And if the response is not available yet, the server’s response field will be null. - If you use
OkHttpLoggingInterceptor
you may notice that the progress at first goes in the blink of an eye from 0% to 100% then it goes once more from 0% to 100% but significantly slower and only after thatonComplete
method is called. As you can read here it happens becauseHttpLoggingInterceptor
loads the data buffer for internal logging purpose by callingwriteTo
method and after that the method is called one more time for uploading the data to the server. The issue can be resolved by removing interceptor or using workaround shown here.
If in doubt please check out my demo project on Github. If you have any questions or suggestions how to resolve that issue in the better way, please let me know!