This week I tried to achieve a simple thing on Android with Retrofit2 : download a file from an API. I realized that it was not so obvious to show the progress of the current download.
In this post I will use Retrofit2, OkHttp3, Okio and RxJava. The original project uses Dagger for injection and is based on an MVP pattern. Some parts of the code were removed to improve understanding and reduce complexity.
Assuming your Retrofit environment is already in place, we can declare a route to download our file. By default, without using converters, Retrofit deserializes the Http responses in a OkHttp3 ResponseBody object.
It was rather simple until then, wasn’t it ? The good news is that we can now download a file from our server like this :
The bad news is that we can not determine the status of the current download until it is complete. This is acceptable for small downloads but in my opinion it is better for the user experience to display some feedback on what is happening.
OkHttp3 gives us a way to read the bytes sent by the server. But if we launch several downloads we have to differentiate them …
I chose to add a custom header in the query. Thus the signature of the Retrofit route becomes:
And we need to pass the identifier from our DownloadService to the Retrofit service:
OkHttp3 allows us to intercept the request and the response through interceptors. We can analyse and modify the ResponseBody directly from them. The idea is to intercept the response, get the number of bytes received and dispatch the response to the next OkHttp3 middleware.
To notify the view from the Interceptor we need to create a Bus in the application. Thanks to my colleague Steve Grosbois for saving me time with his SimpleEventBus class:
We will emit an immutable ProgressEvent from our Interceptor.
The bus and the event are ready, we can now create the interceptor :
We have to create the custom ResponseBody used by the interceptor.
And the interface required by the DownloadProgressResponseBody class to dispatch progress updates.
And now, we can plug the interceptor to the OkHttp3 instance :
For design considerations, we won’t listen directly the bus from our UI. In a MVP architecture the right way to do it is to let the presenter pass a Subscriber to the service and then dispatch the result to the view. We need to change a little bit our DownloadService to achieve this.
And now, the final part, let’s call our stack from the presenter and display the progress in the console or in the view.
You won’t be able to get the progress if the header Content-Length is not set in the response. If you develop the server part too, you should consider adding it everywhere when you create download endpoints.
In the case this is not possible, you can still show to the user the number of bytes downloaded and the speed, it’s always better than an indeterminate progress bar :)
Happy downloading !