Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

android retrofit download progress

Tags:

android

I'm new to retrofit. I've searched but didn't found a simple answer. I want to know how can I show progress of download in Notification bar or at least show a progress dialog which specifies the percent of process and size of downloading file. Here is my code:

public interface ServerAPI {
    @GET
    Call<ResponseBody> downlload(@Url String fileUrl);

    Retrofit retrofit =
            new Retrofit.Builder()
                    .baseUrl("http://192.168.43.135/retro/") 
                    .addConverterFactory(GsonConverterFactory.create())
                    .build();

}

public void download(){
    ServerAPI api = ServerAPI.retrofit.create(ServerAPI.class);
    api.downlload("https://www.google.com/images/branding/googlelogo/2x/googlelogo_color_120x44dp.png").enqueue(new Callback<ResponseBody>() {
        @Override
        public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
            try {
                File path = Environment.getExternalStorageDirectory();
                File file = new File(path, "file_name.jpg");
                FileOutputStream fileOutputStream = new FileOutputStream(file);
                IOUtils.write(response.body().bytes(), fileOutputStream);
            }
            catch (Exception ex){
            }
        }


        @Override
        public void onFailure(Call<ResponseBody> call, Throwable t) {
        }
    });
}

please guide me if you can. thanks

like image 452
Saman Avatar asked Feb 08 '17 16:02

Saman


2 Answers

You need to create a specific OkHttp client which will intercept the network requests and send updates. This client should only be used for downloads.

First you are going to need an interface, like this one:

public interface OnAttachmentDownloadListener {
    void onAttachmentDownloadedSuccess();
    void onAttachmentDownloadedError();
    void onAttachmentDownloadedFinished();
    void onAttachmentDownloadUpdate(int percent);
}

Your download call should return a ResponseBody, which we will extend from to be able to get the download progress.

private static class ProgressResponseBody extends ResponseBody {

    private final ResponseBody responseBody;
    private final OnAttachmentDownloadListener progressListener;
    private BufferedSource bufferedSource;

    public ProgressResponseBody(ResponseBody responseBody, OnAttachmentDownloadListener progressListener) {
        this.responseBody = responseBody;
        this.progressListener = progressListener;
    }

    @Override public MediaType contentType() {
        return responseBody.contentType();
    }

    @Override public long contentLength() {
        return responseBody.contentLength();
    }

    @Override public BufferedSource source() {
        if (bufferedSource == null) {
            bufferedSource = Okio.buffer(source(responseBody.source()));
        }
        return bufferedSource;
    }

    private Source source(Source source) {
        return new ForwardingSource(source) {
            long totalBytesRead = 0L;

            @Override public long read(Buffer sink, long byteCount) throws IOException {
                long bytesRead = super.read(sink, byteCount);

                totalBytesRead += bytesRead != -1 ? bytesRead : 0;

                float percent = bytesRead == -1 ? 100f : (((float)totalBytesRead / (float) responseBody.contentLength()) * 100);

                if(progressListener != null)
                    progressListener.onAttachmentDownloadUpdate((int)percent);

                return bytesRead;
            }
        };
    }
}

Then you will need to create your OkHttpClient like this

public OkHttpClient.Builder getOkHttpDownloadClientBuilder(OnAttachmentDownloadListener progressListener) {
    OkHttpClient.Builder httpClientBuilder = new OkHttpClient.Builder();

    // You might want to increase the timeout
    httpClientBuilder.connectTimeout(20, TimeUnit.SECONDS);
    httpClientBuilder.writeTimeout(0, TimeUnit.SECONDS);
    httpClientBuilder.readTimeout(5, TimeUnit.MINUTES);

    httpClientBuilder.addInterceptor(new Interceptor() {
        @Override
        public Response intercept(Chain chain) throws IOException {
            if(progressListener == null) return chain.proceed(chain.request());

        Response originalResponse = chain.proceed(chain.request());
        return originalResponse.newBuilder()
                .body(new ProgressResponseBody(originalResponse.body(), progressListener))
                .build();
        }
    });

    return httpClientBuilder;
}

Finally you only have to create your Retrofit client a different way, by passing your new OkHttp client. Based on your code, you can use something like this:

 public Retrofit getDownloadRetrofit(OnAttachmentDownloadListener listener) {

    return new Retrofit.Builder()
                .baseUrl("http://192.168.43.135/retro/") 
                .addConverterFactory(GsonConverterFactory.create())
                .client(getOkHttpDownloadClientBuilder(listener).build())
                .build();

}

Your listener will handle the creation of your notification or whatever else you want.

like image 78
maxoumime Avatar answered Sep 21 '22 11:09

maxoumime


Here is another Kotlin solution using Flow

interface MyService {
    @Streaming // allows streaming data directly to fs without holding all contents in ram
    @GET
    suspend fun getUrl(@Url url: String): ResponseBody
}

sealed class Download {
    data class Progress(val percent: Int) : Download()
    data class Finished(val file: File) : Download()
}

fun ResponseBody.downloadToFileWithProgress(directory: File, filename: String): Flow<Download> =
    flow {
        emit(Download.Progress(0))

        // flag to delete file if download errors or is cancelled
        var deleteFile = true
        val file = File(directory, "${filename}.${contentType()?.subtype}")

        try {
            byteStream().use { inputStream ->
                file.outputStream().use { outputStream ->
                    val totalBytes = contentLength()
                    val data = ByteArray(8_192)
                    var progressBytes = 0L

                    while (true) {
                        val bytes = inputStream.read(data)

                        if (bytes == -1) {
                            break
                        }

                        outputStream.channel
                        outputStream.write(data, 0, bytes)
                        progressBytes += bytes

                        emit(Download.Progress(percent = ((progressBytes * 100) / totalBytes).toInt()))
                    }

                    when {
                        progressBytes < totalBytes ->
                            throw Exception("missing bytes")
                        progressBytes > totalBytes ->
                            throw Exception("too many bytes")
                        else ->
                            deleteFile = false
                    }
                }
            }

            emit(Download.Finished(file))
        } finally {
            // check if download was successful

            if (deleteFile) {
                file.delete()
            }
        }
    }
        .flowOn(Dispatchers.IO)
        .distinctUntilChanged()

suspend fun Context.usage() {
    coroutineScope {
        myService.getUrl("https://www.google.com")
            .downloadToFileWithProgress(
                externalCacheDir!!,
                "my_file",
            )
            .collect { download ->
                when (download) {
                    is Download.Progress -> {
                        // update ui with progress
                    }
                    is Download.Finished -> {
                        // update ui with file
                    }
                }
            }
    }
}
like image 23
Robert C. Avatar answered Sep 19 '22 11:09

Robert C.