1

I am doing a POST request with Retrofit2-library to upload files to my api. How can I represent the progress somehow the user knows the app is doing something?

My final goal is to link this to a progress bar or something, but first how to get the progress of POST from retrofit2?

Simson
  • 3,373
  • 2
  • 24
  • 38
maryrio7
  • 310
  • 3
  • 18
  • 1
    Please add some code or at least mention if you are using any third party library for networking. – shubham vashisht Nov 12 '19 at 12:24
  • Two problems you must solve first find the progress from the POST command and second create a progress-bar. – Simson Nov 14 '19 at 13:44
  • This could be a good question if you describe what library you are using for the POST and ask how to get the progress from it. – Simson Nov 14 '19 at 13:47
  • @Simson I am using Retrofit 2 – maryrio7 Nov 14 '19 at 13:56
  • Does this answer your question? [Is it possible to show progress bar when upload image via Retrofit 2?](https://stackoverflow.com/questions/33338181/is-it-possible-to-show-progress-bar-when-upload-image-via-retrofit-2) – Manish Dec 01 '19 at 18:43

1 Answers1

1

If you use multipart for upload:

Create a new class extending RequestBody:

public class ProgressRequestBody extends RequestBody {
    private static final int DEFAULT_BUFFER_SIZE = 2048;
    private UploadProgressListener listener;
    private File file;
    private String path;
    private String content_type;

    public interface UploadProgressListener {
        void onProgressUpdate(int percentage);
    }

public ProgressRequestBody(final File file, String contentType, final  UploadProgressListener listener) {
    this.file = file;
    this.listener = listener;  
    this.content_type = contentType;          
}

@Override
public MediaType contentType() {
    return MediaType.parse(content_type+"/*");
}

@Override
public long contentLength() throws IOException {
  return file.length();
}

@Override
public void writeTo(BufferedSink sink) throws IOException {
    long fileLength = file.length();
    byte[] buffer = new byte[DEFAULT_BUFFER_SIZE];
    FileInputStream in = new FileInputStream(file);
    long uploaded = 0;

try {
            int read;
            Handler handler = new Handler(Looper.getMainLooper());
            while ((read = in.read(buffer)) != -1) {

            // update progress on UI thread
                handler.post(new ProgressUpdater(uploaded, fileLength));

                uploaded += read;
                sink.write(buffer, 0, read);
            }
        } finally {
            in.close();
        }
}

private class ProgressUpdater implements Runnable {
        private long mUploaded;
        private long mTotal;
        public ProgressUpdater(long uploaded, long total) {
            mUploaded = uploaded;
            mTotal = total;
        }

        @Override
        public void run() {
            listener.onProgressUpdate((int)(100 * mUploaded / mTotal));            
        }
    }
}

Then create your api interface:

@Multipart
    @POST("/your_upload_url")        
    Call<YourJsonObject> uploadImage(@Part MultipartBody.Part file);

Use it like this:

  ProgressRequestBody fileBody = new ProgressRequestBody(file, "your content type", new ProgressRequestBody.UploadProgressListener() {
            @Override
            public void onProgressUpdate(int percentage) {
// Update the progressbar
            }
        }););
     MultipartBody.Part filePart = 

     MultipartBody.Part.createFormData("image", file.getName(), fileBody);
    Call<JsonObject> request = RetrofitClient.uploadImage(filepart);

     request.enqueue(new Callback<JsonObject>() {
     @Override
     public void onResponse(Call<JsonObject> call,   Response<JsonObject> response) {
        if(response.isSuccessful()){
     //Do something
          }
    }

    @Override
    public void onFailure(Call<JsonObject> call, Throwable t) {

    }
});

}

Done.

Alternate method using interceptor:

We can add an interceptor to the okhttp client to track the upload progress.

First, you need to extend okhttp's ResponseBody class like this:

import java.io.IOException;

import okhttp3.MediaType;
import okhttp3.ResponseBody;
import okio.Buffer;
import okio.BufferedSource;
import okio.ForwardingSource;
import okio.Okio;
import okio.Source;

public class ProgressResponseBody extends ResponseBody {

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

    ProgressResponseBody(ResponseBody responseBody, ProgressListener 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);
                // read() returns the number of bytes read, or -1 if this source is exhausted.
                totalBytesRead += bytesRead != -1 ? bytesRead : 0;
                progressListener.update(totalBytesRead, responseBody.contentLength(), bytesRead == -1);
                return bytesRead;
            }
        };
    }
}

interface ProgressListener {
    void update(long bytesRead, long contentLength, boolean done);
}

This class is responsible for tracking the progress and updating the listener.

Then you must create a new instance of listener:

final ProgressListener progressListener = new ProgressListener() {
      boolean firstUpdate = true;

      @Override public void update(long bytesRead, long contentLength, boolean done) {
        if (done) {
          System.out.println("completed");
        } else {
          if (firstUpdate) {
            firstUpdate = false;
            if (contentLength == -1) {
              System.out.println("content-length: unknown");
            } else {
              System.out.format("content-length: %d\n", contentLength);
            }
          }

          System.out.println(bytesRead);

          if (contentLength != -1) {
            System.out.format("%d%% done\n", (100 * bytesRead) / contentLength);
          }
        }
      }
    };

Then you must add an interceptor to your okhttp client:

OkHttpClient okHttpClient = new OkHttpClient.Builder()
        .addNetworkInterceptor(chain -> {
          Response originalResponse = chain.proceed(chain.request());
          return originalResponse.newBuilder()
              .body(new ProgressResponseBody(originalResponse.body(), progressListener))
              .build();
        })
        .build();

Finally, add the new client to your retrofit:

Retrofit retrofit = new Retrofit.Builder()
                .baseUrl("some url")
                .client(okHttpClient)
                .build();

Please note that this method tracks all API calls, not just a specific API for upload, to solve this problem, you can pass the request URL to ProgressListener.update() then you can use EventBus to post an event containing the URL and the progress. Feel free to ask if you have any questions about this last part.

P.S. This method is based on official okhttp samples.

Behnam Maboudi
  • 655
  • 5
  • 21
  • Does this answer provide two alternative solutions or is one wrong? If one is wrong delete it, if they are alternative let it say so instead of ***old*** and ***edit*** and describe when one is preferred over the other – Simson Nov 15 '19 at 00:19
  • @Simson They are alternative solutions for different scenarios. I edited my answer to clarify it – Behnam Maboudi Nov 15 '19 at 13:01