Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Download and write a file with Retrofit and RxJava

I'm downloading a pdf file with retrofit, the way that i'm downloading it is by blocks. I use the Content-Range header to obtain a range of bytes, then i need to write these bytes on a file the problem is the order to write them. I'm using the flatMap() function to return an observable for each request that must be done to download the file.

.flatMap(new Func1<Integer, Observable<Response>>() {
                @Override
                public Observable<Response> call(Integer offset) {
                    int end;

                    if (offset + BLOCK_SIZE > (contentLength - 1))
                        end = (int) contentLength - 1 - offset;

                    else
                        end = offset + BLOCK_SIZE;

                    String range = getResources().getString(R.string.range_format, offset, end);

                   return ApiAdapter.getApiService().downloadPDFBlock(range);
                }
            })

The downloadPDFBlock receive an strings that is needed by a header : Range: bytes=0-3999. Then i use subscribe function to write the bytes downloaded

subscribe(new Subscriber<Response>() {
                @Override
                public void onCompleted() {
                    Log.i(LOG_TAG, file.getAbsolutePath());
                }

                @Override
                public void onError(Throwable e) {
                    e.printStackTrace();
                }

                @Override
                public void onNext(Response response) {
                    writeInCache(response);
                    }
                }
            });

But the problem is that the writing process is made unordered. For example: if the Range: bytes=44959-53151 is downloaded first, these will be the bytes that will be written first in the file. I have read about BlockingObserver but i don't know if that could be a solution.

I hope you could help me.

like image 846
Silmood Avatar asked Jun 29 '15 22:06

Silmood


People also ask

How do you use RxJava with retrofit?

To use RxJava in retrofit environment we need to do just two major changes: Add the RxJava in Retrofit Builder. Use Observable type in the interface instead of Call.

What is the difference between RxJava and retrofit?

Rx gives you a very granular control over which threads will be used to perform work in various points within a stream. To point the contrast here already, basic call approach used in Retrofit is only scheduling work on its worker threads and forwarding the result back into the calling thread.

What is retrofit RxJava?

Retrofit is a HTTP Client for Android and Java developed by Square. We are going to integrate Retrofit with RxJava to simplify threading in our app. In addition, we will also integrate RxAndroid to make network calls.

What is the difference between coroutines and RxJava?

Coming to RxJava, RxJava streams are prone to leaks, where a stream continues to process items even when you no longer care. Kotlin coroutines use structured concurrency, which makes it much easier to manage the lifecycle of all your concurrent code.


1 Answers

Here is a good example for downloading a file and save it to disk in Android.

Here is a modification of the above linked example without using the lambda expression.

The Retrofit 2 interface, the @Streaming for downloading large files.

public interface RetrofitApi {
    @Streaming
    @GET
    Observable<Response<ResponseBody>> downloadFile(@Url String fileUrl);
}

The code for downloading a file and saving it to disk using Retrofit 2 and rxjava. Update the baseUrl and the url path in the code below to your actual url of the file you need to download.

public void downloadZipFile() {
    Retrofit retrofit = new Retrofit.Builder()
            .baseUrl("https://my.resources.com/")
            .client(new OkHttpClient.Builder().build())
            .addCallAdapterFactory(RxJavaCallAdapterFactory.create()).build();
    RetrofitApi downloadService = retrofit.create(RetrofitApi.class);

    downloadService.downloadFile("resources/archive/important_files.zip")
            .flatMap(new Func1<Response<ResponseBody>, Observable<File>>() {
                @Override
                public Observable<File> call(final Response<ResponseBody> responseBodyResponse) {
                    return Observable.create(new Observable.OnSubscribe<File>() {
                        @Override
                        public void call(Subscriber<? super File> subscriber) {
                            try {
                                // you can access headers of response
                                String header = responseBodyResponse.headers().get("Content-Disposition");
                                // this is specific case, it's up to you how you want to save your file
                                // if you are not downloading file from direct link, you might be lucky to obtain file name from header
                                String fileName = header.replace("attachment; filename=", "");
                                // will create file in global Music directory, can be any other directory, just don't forget to handle permissions
                                File file = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).getAbsoluteFile(), fileName);

                                BufferedSink sink = Okio.buffer(Okio.sink(file));
                                // you can access body of response
                                sink.writeAll(responseBodyResponse.body().source());
                                sink.close();
                                subscriber.onNext(file);
                                subscriber.onCompleted();
                            } catch (IOException e) {
                                e.printStackTrace();
                                subscriber.onError(e);
                            }
                        }
                    });
                }
            })
            .subscribeOn(Schedulers.io())
            .observeOn(AndroidSchedulers.mainThread())
            .subscribe(new Observer<File>() {
                            @Override
                            public void onCompleted() {
                                Log.d("downloadZipFile", "onCompleted");
                            }

                            @Override
                            public void onError(Throwable e) {
                                e.printStackTrace();
                                Log.d("downloadZipFile", "Error " + e.getMessage());
                            }

                            @Override
                            public void onNext(File file) {
                                Log.d("downloadZipFile", "File downloaded to " + file.getAbsolutePath());
                            }
                       });
}
like image 117
s-hunter Avatar answered Oct 02 '22 01:10

s-hunter