简体   繁体   中英

How to present a progress screen before/while a long running operation starts?

I am well aware that blocking the UI is not a good idea in general but there are some scenarios where my app simply cannot do any other work until some long running operations (eg loading data from a server) are complete.

Assume the user clicks the "Load Data" button. To indicate that no UI interaction is possible until the data is loaded I would like to grey out the screen and show some kind of activity indicator. This is no problem at all, I simply overlay the screen with a new Fragment.

The question is: How can I present this overlay fragment?

public void onLoadDataClick() {
    // grey out the screen by simple showing a new Fragment
    showActivityIndicatorOverlay();

    // Start the long running opeartion
    doVeryMuchWork();

    dismissActivityIndicatorOverlay();
}

public void showActivityIndicatorOverlay() {
    FragmentTransaction ft = context.getSupportFragmentManager().beginTransaction();
    ActivityIndicatorOverlayFragment overlayFragment = ActivityIndicatorOverlayFragment.newInstance("Loading Data");        
    overlayFragment.show(ft, "activityIndicator");
} 

This does NOT work. The overlay does not show up. If I remove dismissActivityIndicatorOverlay() the overlay shows up after the long running operation completed. This is not too suprising: I assume that the showing the new fragment is handeled at the end of the current run-loop or at the start of the next loop. Of course the long running operation has to complete before the run-loop ends and thus the overlay is displayed too late...

The obvious solution is of course to run the operation in a background thread using an AsyncTask :

public void onLoadDataClick() {
    LoadDataTask loadTask = new LoadDataTask();
    loadTask.execute();
}

private class LoadDataTask extends AsyncTask<Void, Void, Void> {
    @Override
    protected void onPreExecute() {
        showActivityIndicatorOverlay();
    }

    @Override
    protected Void doInBackground(Void... params) {
        doVeryMuchWork();
    }

    @Override
    protected void onPostExecute() {
        dismissActivityIndicatorOverlay();
    }
}

I was surprised, that this solution doesn't work either. It behaves exactly like the first approach: The overlay does not appear. When onPostExecute() is removed the overlay appears after the operation is complete. the Why is that?

What is the correct solution to present such an activity indicator?

I'd suggest the use of a ProgessDialog .

Declare a ProgressDialog as an instance variable. Something like : ProgressDialog pDialog;

then inside onCreate() :

pDialog = new ProgressDialog(this);
//The next two methods will ensure that the user is unable to 
//cancel the Progress Dialog unless you explicitly 
//do so by calling `pDialog.dismiss();`
pDialog.setCancelable(false);
pDialog.setCanceledOnTouchOutside(false);

Modify AsyncTask somewhat like this :

private class LoadDataTask extends AsyncTask<Void, Void, Void> {
    @Override
    protected void onPreExecute() {
        pDialog.setMessage("Loading Data.. Please Wait.");
        pDialog.show();
    }

    @Override
    protected Void doInBackground(Void... params) {
        doVeryMuchWork();
    }

    @Override
    protected void onPostExecute() {
        pDialog.dismiss();
    }
}

A ProgressDialog is very much the canonical solution for these cases...

private class LoadDataTask extends AsyncTask<Void, Void, Void> {
    private ProgressDialog mProgress;

    @Override
    protected void onPreExecute() {
        mProgress = new ProgressDialog(context);
        mProgress.setTitle("So much to do");
        mProgress.setMessage("Doing very much work");
        mProgress.setIndeterminate(true);
        mProgress.setCancelable(false);
        mProgress.show();
    }

    @Override
    protected Void doInBackground(Void... params) {
        doVeryMuchWork();
    }

    @Override
    protected void onPostExecute() {
        mProgress.dismiss();
    }
}   

As for your attempted solutions, the first one does not work for the very reasons you state. The second should , though.

You can do something like this, if you're not doing heavy stuff precisely on setContentView(R.layout.main)

public void onCreate(Bundle savedInstanceState){
    super.onCreate(savedInstanceState);
    setContentView(R.layout.splash);

    handler = new Handler();

    new AsyncTask<Void, Void, Void>() {

    @Override
    protected Void doInBackground(Void... params) {
            //Do some heavy stuff
            return null;
        } 

        @Override
        public void onPostExecute(Void result){
            handler.post(new Runnable(){
                 @Override
                 public void run(){
                     setContentView(R.layout.main);
                 }
            });
        }
   }.execute();
}

OR you can use progress dialog

private class LongTask extends AsyncTask<Void, Void, Void> {
private ProgressDialog pd;
Context context;

public LongTask(Context c)
{

     this.context = c;
}

@Override
protected void onPreExecute() {
    pd = new ProgressDialog(context);
    pd.setTitle("Please wait...!");
    pd.setMessage("Loding the information");
    pd.setIndeterminate(true);
    pd.setCancelable(false);
    pd.show();
}

@Override
protected Void doInBackground(Void... params) {
    // do your heavy tasks here
}

@Override
protected void onPostExecute() {
    if (pd.isShowing())
     pd.dismiss();
}

}

Call AsyncTask like this

new LongTask(your_activity.this).execute();

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM