4

I have used Retrofit2 for file download. I am not able to update ProgressBar with progress value. I got progress value. So there is not issue. When I set the progress value to progress bar not reflected in UI.

I am talking about Progress Bar which is present inside RecyclerView Adapter.

Below is my retrofit call, And this method will be called when clicking a item inside RecyclerView.

 private void downloadFileFromServer(String otpapi, String userName, String password, String code, String vmFileName, String filePath, String vmFileSize, int position, CircularProgressBar circularProgress) {
      
        GetDataForApiCall getDataForApiCall= RetrofitInstance.getRetrofit(url,otpapi,context).create(GetDataForApiCall.class);
      
        Call<ResponseBody> downloadVoicemail=  getDataForApiCall.downloadVoiceMail(userName,password,code,vmFileName);
        this.circularProgressBar=circularProgress;
        this.circularProgressBar.setIndeterminate(false);
        this.circularProgressBar.setProgress(0);
        this.circularProgressBar.setMax(100);
        this.circularProgressBar.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                AndroidLogger.log(5,"onClick","circularProgressBar onClick executed!!");
                Toast.makeText(context,"cancel clicked",Toast.LENGTH_LONG).show();
                cancelDownload = true;
            }
        });
        downloadVoicemail.enqueue(new Callback<ResponseBody>() {
            @Override
            public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {

boolean downloadResult = writeResponseBodyToDisk(response.body(),vmFileSize,filePath);
                if(downloadResult) {
                    Toast.makeText(context, "File downloaded", Toast.LENGTH_SHORT).show();
                    updateVoiceMailFilePath(position, filePath);
                    updateViews(position);
                }else {
                    deleteVoiceMailFileFromLocalSystem(filePath);
                    updateViews(position);
                }

            }

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

            }
        });
    }

private boolean writeResponseBodyToDisk( ResponseBody body, String fileSize, String filePath) {
        try {
            InputStream inputStream = null;
            OutputStream outputStream = null;

            try {
                byte[] fileReader = new byte[8192];
                //long fileSize = body.contentLength();
                long fileSizeDownloaded = 0;
                long lengthOfFile = Long.parseLong( String.format( "%.0f",Double.parseDouble(fileSize )) )  * 1024;
                AndroidLogger.log(5,TAG,"filesize"+fileSize + "length of file"+lengthOfFile);
                inputStream = body.byteStream();
                outputStream = new FileOutputStream(filePath);

                while (true) {
                    int read = inputStream.read(fileReader);

                    if(cancelDownload){
                        inputStream.close();
                        return false;
                    }
                    if (read == -1) {
                        AndroidLogger.log(5,TAG,"-1 value so break");
                        break;
                    }
                    outputStream.write(fileReader, 0, read);

                    fileSizeDownloaded += read;
                    if(lengthOfFile >0) {
                    AndroidLogger.log(5,TAG,"FileSize downloaded"+ fileSizeDownloaded);
                        int progress = (int) (fileSizeDownloaded * 100 / lengthOfFile);
                    AndroidLogger.log(5,TAG,"Length of  file"+ lengthOfFile);
                    AndroidLogger.log(5,TAG,"Progress"+ progress);
                    this.circularProgressBar.setProgress(progress);
                        update(progress);
                    }
                    AndroidLogger.log(5,TAG, "file download: " + fileSizeDownloaded + " of " + fileSize);
                }

                outputStream.flush();

                return true;
            } catch (IOException e) {
                return false;
            } finally {
                if (inputStream != null) {
                    inputStream.close();
                }

                if (outputStream != null) {
                    outputStream.close();
                }
            }
        } catch (IOException e) {
            return false;
        }
    }

Also I have tried Listener to update value because retrofit call done on some other thread. So for update UI I have used listener which is not helped.

I am using Retrofit2 for making API calls. So for updating UI in all Activities I had used interface listeners. This works perfect for all Activities. But when I tried the same thing in RecyclerView Adapter class, not able to update progress bar. Before calling api I had set Progress bar to 0 and max to 100.

Below case Works fine,

Circular ProgressBar before API call set to Zero

Circular ProgressBar after download Completed will change to a tick mark

Below is not Working,

Circular ProgressBar with indication of loading

NOTE: I am facing this issue only when used Retrofit2 to make API call. If I used normal HTTPUrlConnection for making API call inside a Asynctask, then progress loading working fine.

I have checked whether the progress updation is occurs on main thread or not by below code,

if(Looper.myLooper() == Looper.getMainLooper()) {
circularProgressBar.setProgress(progress);
}

The above if condition is satisfied. Eventhough Progress bar not updated.

Also I have tried below,

Handler mainHandler = new Handler(Looper.getMainLooper());

                        Runnable myRunnable = new Runnable() {
                            @Override
                            public void run() {
                                AndroidLogger.log(5,TAG,"Running on UI thread");
                                circularProgressBar.setProgress(progress);
                            } 
                        };
                        mainHandler.post(myRunnable);


                    }

I have placed this inside writeResponseBodyToDisk method, inside while loop, But it was called only two times and progress bar not updated.

I commented the part, where the progress bar loading will change to a tick mark. After that when I tried download,once download completed able to see 100 percent download completed in progress bar. Before progress percent updation not reflected.

Please Anybody help me out to solve this issue.

Thanks in advance.

Kousalya
  • 700
  • 10
  • 29

3 Answers3

4

The UI updates are needed to be happened in the UI thread. Setting the color from a background thread (i.e. AsyncTask) will not actually update the UI as this is not happening in the UI thread. There are several ways to update the progress color in the UI. I would recommend having an interface along with a callback function so that you can invoke that callback function to update the UI from the activity of fragment that implemented it. Here's a clarification.

Let us declare an interface first.

public interface UIUpdater {
    void updateUI(int progressValue);
}

Now implement this interface in the activity or fragment where you want to update the UI.

public class MainActivity extends Activity implements UIUpdater {

    @Override
    public void updateUI(int progressValue) {
        // Do the UI update here. 
        circularProgress.setProgress(progressValue); // Or anything else that you want to do. 
    }
}

You want to modify the constructor of initializing your AsyncTask to have the UIUpdater class to be passed as parameter.

public class YourAsyncTask extends AsyncTask<String, Void, String> {

    UIUpdater listener;

    public YourAsyncTask(UIUpdater listener) {
        this.listener = listener;
    }
}

So that you can call the AsyncTask from the activity/fragment using something as following.

YourAsyncTask myTask = new YourAsyncTask(this); // When you are passing this from activity, you are implicitly passing the interface that it implemented. 
myTask.execute(); 

Now from the async task, while you are publishing the progress, invoke the listener function in order to update the UI using the UI thread of your activity/fragment.

protected void onProgressUpdate(Integer... values) {
    super.onProgressUpdate(values);

    try {
       listener.updateUI(values[0]);
    }
} 

Hope you get the idea.

Reaz Murshed
  • 23,691
  • 13
  • 78
  • 98
  • I am having progress bar in my recylerview item.. I am assigning progress inside recycler adapter..Thatswhy I doesn't know how to use listener in that case. – Kousalya Aug 30 '20 at 16:20
  • You can have the similar implementation for the adapters as well. The adapter can implement the `UIUpdater` and can override the same method in it. You might have to modify the interface to take an extra parameter for position to work with the `RecyclerView` if that helps. – Reaz Murshed Aug 30 '20 at 16:46
  • thank you for your answer :-).but not able to solve my pbm.I am doing Asynctas inside Retrofit2 response. May be that is the problem think so. – Kousalya Aug 31 '20 at 01:59
  • overrided method is called but not reflected in UI. So tried different things like using handler,runOnUIthread. But nothing helped – Kousalya Aug 31 '20 at 03:22
0

Your problem is not that you are using a RecyclerView, your problem is that you're not using it correctly.

It's is neither the adapter, nor the view (RecyclerView) nor even the ViewHolder's responsibility to determine anything here.

  • I assume you have a ViewHolder type with a progress bar in it.
  • I assume you have a ListAdapter<T, K> with the corresponding DiffUtilCallback implementation.
  • I assume progress is handled/reported elsewhere (in your repository, through a viewModel or Presenter, via a useCase, Interactor, or even the plain ViewModel).
  • Your adapter now has nothing to do but wait.
  • When progress is updated, you prepare the List you are displaying so the item whose progress has changed is updated.
  • Afterwards you submit this new list (with the new progress) to your adapter.
  • Your DiffUtil calculates this (it can be async too!)
  • Your RecyclerView is magically updated, no need to hack anything.

If any of these is not true, perform the necessary adjustments so your code can be properly tested and the concerns of each piece of code is better separated.

Think about it this way, imagine you had all that, and there was a small bug in the "progress value" you update. Which would be easier, search in the little function in your ViewModel or UseCase/interactor that transforms the Retrofit value into your <Thing>, or all over the place in your spaghetti code of callbacks?

Martin Marconcini
  • 26,875
  • 19
  • 106
  • 144
  • thank you for your answer. In my case I am doing retrofit call inside a onclick listener of an item. I am updating progress bar inside a while loop in writeResponseBodyToDisk method. But not reflected in UI. – Kousalya Sep 09 '20 at 14:18
  • How are you telling your adapter that the data changed? – Martin Marconcini Sep 10 '20 at 14:43
  • thank you for your help :-) I found a solution. Will post that here later. – Kousalya Sep 10 '20 at 14:45
  • No worries, glad to know you found a solution. If you are interested, I modified an unrelated example to show a progress bar during a "fake" slow operation. You can look at the "diff" between master (or a topic branch that had viewMOdels) and this new branch here https://github.com/Gryzor/CheckBoxCounter/compare/simulatedProgressBars?expand=1 If you run it, you'll notice you can tap checkboxes and a progress will appear, once they are "done" they are updated again to show the correct UI. The "Counter" is irrelevant. :) Good luck. – Martin Marconcini Sep 10 '20 at 14:52
0

Thank you all for trying to help me!!

This answer helped me to update Circular ProgressBar https://stackoverflow.com/a/42119419/11630822

public static Retrofit getDownloadRetrofit(String baseUrl, DownloadVoicemailListener listener) {

        return new Retrofit.Builder()
                .baseUrl(baseUrl)
                .addConverterFactory(GsonConverterFactory.create())
                .client(getOkHttpDownloadClientBuilder(listener).build())
                .build();

    }

    private static OkHttpClient.Builder getOkHttpDownloadClientBuilder(DownloadVoicemailListener listener) {

        OkHttpClient.Builder httpClientBuilder = new OkHttpClient.Builder().connectionSpecs(Collections.singletonList(getConnectionSpec()));

        HttpLoggingInterceptor logging = new HttpLoggingInterceptor();
        if (!releaseMode) {

            logging.level(HttpLoggingInterceptor.Level.BODY);
            httpClientBuilder.addInterceptor(logging);
        }

        httpClientBuilder.connectTimeout(20, TimeUnit.SECONDS);
        httpClientBuilder.writeTimeout(0, TimeUnit.SECONDS);
        httpClientBuilder.readTimeout(5, TimeUnit.MINUTES);

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

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

        return httpClientBuilder;
    }

In Progressbody class,


public class ProgressResponseBody extends ResponseBody {
    private final String TAG=ProgressResponseBody.class.getSimpleName();
    private  ResponseBody responseBody;
    private BufferedSource bufferedSource;

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

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

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

    @NotNull
    @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 {

                return byteCount;
            }
        };
    }


}


@Override
    public void readFile(ResponseBody responseBody) {
        boolean result = writeResponseBodyToDisk(responseBody);
       
    }
private boolean writeResponseBodyToDisk(ResponseBody body) {
        try {

            InputStream inputStream = null;
            OutputStream outputStream = null;

            try {
                long fileSizeDownloaded = 0;
                AndroidLogger.log(5, TAG, "File path" + filePath);
                AndroidLogger.log(5, TAG, "File size" + fileSize);
                long lengthOfFile = Long.parseLong(String.format("%.0f", Double.parseDouble(this.fileSize))) * 1024;
                AndroidLogger.log(5, TAG, "filesize" + fileSize + "length of file" + lengthOfFile);
                inputStream = body.byteStream();
                outputStream = new FileOutputStream(this.filePath);

                byte[] data = new byte[4096];
                long total = 0;
                int count;
                while ((count = inputStream.read(data)) != -1) {
                    if (cancelDownload) {
                        AndroidLogger.log(5,TAG,"Cancel download clicked");
                        inputStream.close();
                        return false;
                    }
                    total += count;
                    outputStream.write(data, 0, count);
                    fileSizeDownloaded += count;
                    if (lengthOfFile > 0) {
                        AndroidLogger.log(5, TAG, "FileSize downloaded" + fileSizeDownloaded);
                        int progress = (int) (fileSizeDownloaded * 100 / lengthOfFile);
                        AndroidLogger.log(5, TAG, "Length of  file" + lengthOfFile);
                        AndroidLogger.log(5, TAG, "Progress" + progress);
                        ((Activity) context).runOnUiThread(new Runnable() {
                            @Override
                            public void run() {
                                circularProgressBar.setProgress(progress);
                            }
                        });

                    }
                }
                AndroidLogger.log(5, TAG, "file download: " + fileSizeDownloaded + " of " + fileSize);
                outputStream.flush();
                return true;
            } catch (IOException e) {
                e.printStackTrace();
                return false;
            } finally {
                if (inputStream != null) {
                    inputStream.close();
                }

                if (outputStream != null) {
                    outputStream.close();
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
            return false;
        }
    }
Kousalya
  • 700
  • 10
  • 29