Is it possible to show progress bar when upload image via Retrofit 2? Is it possible to show progress bar when upload image via Retrofit 2? android android

Is it possible to show progress bar when upload image via Retrofit 2?


First of all, you should use Retrofit 2 version equal to or above 2.0 beta2.Second, create new class extends RequestBody:

public class ProgressRequestBody extends RequestBody {    private File mFile;    private String mPath;    private UploadCallbacks mListener;    private String content_type;  private static final int DEFAULT_BUFFER_SIZE = 2048;    public interface UploadCallbacks {        void onProgressUpdate(int percentage);        void onError();        void onFinish();    }

Take note, I added content type so it can accommodate other types aside image

public ProgressRequestBody(final File file, String content_type,  final  UploadCallbacks listener) {    this.content_type = content_type;    mFile = file;    mListener = listener;            }@Override    public MediaType contentType() {        return MediaType.parse(content_type+"/*");    }@Overridepublic long contentLength() throws IOException {  return mFile.length();}@Overridepublic void writeTo(BufferedSink sink) throws IOException {    long fileLength = mFile.length();    byte[] buffer = new byte[DEFAULT_BUFFER_SIZE];    FileInputStream in = new FileInputStream(mFile);    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() {            mListener.onProgressUpdate((int)(100 * mUploaded / mTotal));                    }    }}

Third, create the interface

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

/* JsonObject above can be replace with you own model, just want to make this notable. */

Now you can get progress of your upload. In your activity (or fragment):

class MyActivity extends AppCompatActivity implements ProgressRequestBody.UploadCallbacks {        ProgressBar progressBar;        @Override        protected void onCreate(Bundle savedInstanceState) {            super.onCreate(savedInstanceState);            progressBar = findViewById(R.id.progressBar);            ProgressRequestBody fileBody = new ProgressRequestBody(file, this);            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()){                /* Here we can equally assume the file has been downloaded successfully because for some reasons the onFinish method might not be called, I have tested it myself and it really not consistent, but the onProgressUpdate is efficient and we can use that to update our progress on the UIThread, and we can then set our progress to 100% right here because the file already downloaded finish. */                  }            }            @Override            public void onFailure(Call<JsonObject> call, Throwable t) {                      /* we can also stop our progress update here, although I have not check if the onError is being called when the file could not be downloaded, so I will just use this as a backup plan just in case the onError did not get called. So I can stop the progress right here. */            }        });      }        @Override        public void onProgressUpdate(int percentage) {            // set current progress            progressBar.setProgress(percentage);        }        @Override        public void onError() {            // do something on error        }        @Override        public void onFinish() {            // do something on upload finished,            // for example, start next uploading at a queue            progressBar.setProgress(100);        }}


Modified Yuriy Kolbasinskiy's to use rxjava and use kotlin.Added a workaround for using HttpLoggingInterceptor at the same time

class ProgressRequestBody : RequestBody {    val mFile: File    val ignoreFirstNumberOfWriteToCalls : Int    constructor(mFile: File) : super(){        this.mFile = mFile        ignoreFirstNumberOfWriteToCalls = 0    }    constructor(mFile: File, ignoreFirstNumberOfWriteToCalls : Int) : super(){        this.mFile = mFile        this.ignoreFirstNumberOfWriteToCalls = ignoreFirstNumberOfWriteToCalls    }    var numWriteToCalls = 0    protected val getProgressSubject: PublishSubject<Float> = PublishSubject.create<Float>()    fun getProgressSubject(): Observable<Float> {        return getProgressSubject    }    override fun contentType(): MediaType {        return MediaType.parse("video/mp4")    }    @Throws(IOException::class)    override fun contentLength(): Long {        return mFile.length()    }    @Throws(IOException::class)    override fun writeTo(sink: BufferedSink) {        numWriteToCalls++        val fileLength = mFile.length()        val buffer = ByteArray(DEFAULT_BUFFER_SIZE)        val `in` = FileInputStream(mFile)        var uploaded: Long = 0        try {            var read: Int            var lastProgressPercentUpdate = 0.0f            read = `in`.read(buffer)            while (read != -1) {                uploaded += read.toLong()                sink.write(buffer, 0, read)                read = `in`.read(buffer)                // when using HttpLoggingInterceptor it calls writeTo and passes data into a local buffer just for logging purposes.                // the second call to write to is the progress we actually want to track                if (numWriteToCalls > ignoreFirstNumberOfWriteToCalls ) {                    val progress = (uploaded.toFloat() / fileLength.toFloat()) * 100f                    //prevent publishing too many updates, which slows upload, by checking if the upload has progressed by at least 1 percent                    if (progress - lastProgressPercentUpdate > 1 || progress == 100f) {                        // publish progress                        getProgressSubject.onNext(progress)                        lastProgressPercentUpdate = progress                    }                }            }        } finally {            `in`.close()        }    }    companion object {        private val DEFAULT_BUFFER_SIZE = 2048    }}

An example video upload interface

public interface Api {    @Multipart    @POST("/upload")            Observable<ResponseBody> uploadVideo(@Body MultipartBody requestBody);}

An example function to post a video:

fun postVideo(){            val api : Api = Retrofit.Builder()            .client(OkHttpClient.Builder()                    //.addInterceptor(HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY))                    .build())            .baseUrl("BASE_URL")            .addCallAdapterFactory(RxJava2CallAdapterFactory.create())            .build()            .create(Api::class.java)    val videoPart = ProgressRequestBody(File(VIDEO_URI))    //val videoPart = ProgressRequestBody(File(VIDEO_URI), 1) //HttpLoggingInterceptor workaround    val requestBody = MultipartBody.Builder()            .setType(MultipartBody.FORM)            .addFormDataPart("example[name]", place.providerId)            .addFormDataPart("example[video]","video.mp4", videoPart)            .build()    videoPart.getProgressSubject()            .subscribeOn(Schedulers.io())            .subscribe { percentage ->                Log.i("PROGRESS", "${percentage}%")            }    var postSub : Disposable?= null    postSub = api.postVideo(requestBody)            .subscribeOn(Schedulers.io())            .observeOn(AndroidSchedulers.mainThread())            .subscribe({ r ->            },{e->                e.printStackTrace()                postSub?.dispose();            }, {                Toast.makeText(this,"Upload SUCCESS!!",Toast.LENGTH_LONG).show()                postSub?.dispose();            })}


Here's how to handle upload file progress with a simple POST rather than Multipart. For multipart check out @Yariy's solution. Additionally, this solution uses Content URI's instead of direct file references.

RestClient

@Headers({    "Accept: application/json",    "Content-Type: application/octet-stream"})@POST("api/v1/upload")Call<FileDTO> uploadFile(@Body RequestBody file);

ProgressRequestBody

public class ProgressRequestBody extends RequestBody {    private static final String LOG_TAG = ProgressRequestBody.class.getSimpleName();    public interface ProgressCallback {        public void onProgress(long progress, long total);    }    public static class UploadInfo {        //Content uri for the file        public Uri contentUri;        // File size in bytes        public long contentLength;    }    private WeakReference<Context> mContextRef;    private UploadInfo mUploadInfo;    private ProgressCallback mListener;    private static final int UPLOAD_PROGRESS_BUFFER_SIZE = 8192;    public ProgressRequestBody(Context context, UploadInfo uploadInfo, ProgressCallback listener) {        mContextRef = new WeakReference<>(context);        mUploadInfo =  uploadInfo;        mListener = listener;    }    @Override    public MediaType contentType() {        // NOTE: We are posting the upload as binary data so we don't need the true mimeType        return MediaType.parse("application/octet-stream");    }    @Override    public void writeTo(BufferedSink sink) throws IOException {        long fileLength = mUploadInfo.contentLength;        byte[] buffer = new byte[UPLOAD_PROGRESS_BUFFER_SIZE];        InputStream in = in();        long uploaded = 0;        try {            int read;            while ((read = in.read(buffer)) != -1) {                mListener.onProgress(uploaded, fileLength);                uploaded += read;                sink.write(buffer, 0, read);            }        } finally {            in.close();        }    }    /**     * WARNING: You must override this function and return the file size or you will get errors     */    @Override    public long contentLength() throws IOException {        return mUploadInfo.contentLength;    }    private InputStream in() throws IOException {        InputStream stream = null;        try {            stream = getContentResolver().openInputStream(mUploadInfo.contentUri);                    } catch (Exception ex) {            Log.e(LOG_TAG, "Error getting input stream for upload", ex);        }        return stream;    }    private ContentResolver getContentResolver() {        if (mContextRef.get() != null) {            return mContextRef.get().getContentResolver();        }        return null;    }}

To initiate the upload:

// Create a ProgressRequestBody for the fileProgressRequestBody requestBody = new ProgressRequestBody(    getContext(),    new UploadInfo(myUri, fileSize),    new ProgressRequestBody.ProgressCallback() {        public void onProgress(long progress, long total) {            //Update your progress UI here            //You'll probably want to use a handler to run on UI thread        }    });// UploadmRestClient.uploadFile(requestBody);

Warning, if you forget to override the contentLength() function you may receive a few obscure errors:

retrofit2.adapter.rxjava.HttpException: HTTP 503 client read error

Or

Write error: ssl=0xb7e83110: I/O error during system call, Broken pipe

Or

javax.net.ssl.SSLException: Read error: ssl=0x9524b800: I/O error during system call, Connection reset by peer

These are a result of RequestBody.writeTo() being called multiple times as the default contentLength() is -1.

Anyways this took a long time to figure out, hope it helps.

Useful links:https://github.com/square/retrofit/issues/1217