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:

  1. Create a new file from filePath.
  2. Add file to RequestBody with a suitable media type.
  3. Create MultipartBody.Part with form data name, file name and RequestBody.

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:

  1. Create a new file from filePath. the same
  2. Add file to RequestBody with a suitable media type. the same
  3. Wrap RequestBody into CountingRequestBody. Create and pass listener to it and call emitter.onNext(progress) when progress changed. ← new step
  4. Create MultipartBody.Part with form data name, file name and RequestBody. 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> from Single<ResponseBody> I ignored response from the server. If you need the data from the response you can make uploadImage method return Flowable<SomeResponseClass> instead of Double 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 that onComplete method is called. As you can read here it happens because HttpLoggingInterceptor loads the data buffer for internal logging purpose by calling writeTo 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!