João Miranda
João Miranda

Reputation: 53

How to disable Button while AsyncTask is running? (Android)

I am trying to disable a button while a download task is being executed. I have tried using setEnabled, setVisibility and setClickable. I think I tried all combinations of these options. All of them disable the button click events while the task is performing, but the events are still being registered somehow, and when I reactive the button, the handler is called if I clicked the button while it was disabled... even if it was invisible or "gone"! (not sure if it is called a handler, I want to refer to the onClick method).

I have also inserted a counter and a Log to verify what I've stated above. The code is shown below. This piece of code if(counter>1) return; is meant to stop the crash, but I would like to remove it, since I want to re-enable the button, and not disable it forever.

onClick:

public void downloadOnClick(View v) {
    counter++;
    Log.d(this.getLocalClassName(), "Button was clicked " + counter + " times.");
    if(counter>1) return;

    ConnectivityManager connMgr = (ConnectivityManager)
            getSystemService(Context.CONNECTIVITY_SERVICE);
    NetworkInfo networkInfo = connMgr.getActiveNetworkInfo();
    if (networkInfo != null && networkInfo.isConnected()) {
        //mButton.setVisibility(View.GONE);
        mButton.setEnabled(false);
        //mButton.setClickable(false);
        mTextView.setText("Getting html file...");
        // if we use simple http, we will need to handle redirect status code
        new DownloadWebpageTask().execute("https://www.google.com/");
    } else {
        mTextView.setText("No network connection available.");
    }
}

AsyncTask:

private class DownloadWebpageTask extends AsyncTask<String, Void, String> {

    private HttpURLConnection mConnection;

    @Override
    protected String doInBackground(String... urls) {
        try {
            URL url = new URL(urls[0]);
            mConnection = (HttpURLConnection) url.openConnection();
            mConnection.setReadTimeout(10000 /* milliseconds */);
            mConnection.setConnectTimeout(15000 /* milliseconds */);
            mConnection.setRequestMethod("GET");
            mConnection.setDoInput(true);

            mConnection.connect();
            int statusCode = mConnection.getResponseCode();
            if (statusCode != HttpURLConnection.HTTP_OK) {
                return "Error: Failed getting update notes";
            }

            return readTextFromServer(mConnection);

        } catch (IOException e) {
            return "Error: " + e.getMessage();
        }
    }

    private String readTextFromServer(HttpURLConnection connection) throws IOException {
        InputStreamReader stream = null;
        try {
            stream = new InputStreamReader(connection.getInputStream());
            BufferedReader br = new BufferedReader(stream);
            StringBuilder sb = new StringBuilder();
            String line = br.readLine();
            while (line != null) {
                sb.append(line + "\n");
                line = br.readLine();
            }
            return sb.toString();
        } finally {
            if (stream != null) {
                stream.close();
            }
        }
    }

    @Override
    protected void onPostExecute(String result) {
        mTextView.setText(result);
        // Can not reactivate button / cancel (pending?) events....
        //mButton.setVisibility(View.VISIBLE);
        mButton.setEnabled(true);
        //mButton.setClickable(true);
    }
}

The full project (it is very simple, just a training example) is available to test in this repository that I have just created.

To conclude, from what I have read, there is in fact a problem regarding button disabling. Mostly this is resolved through the use of a flag to call the onClick method only when the flag is true. Although, this does not solve the problem of re-enabling the button. I have also tried mButton.cancelPendingInputEvents(); but it does not work (and I do not know why. Click events are not yet registered? Or they are not pending?

Is there a simple solution to this problem? Any ideas? Am I missing some basic detail? If not, I am considering trying to create a new button programatically to contour the problem. If I do not keep references to old buttons, are they deleted through garbage collection?

Thanks in advance!

[Edit] Clarification:

Since the title could be misleading in this point, I want to clarify that I am able to disable and re-enable the button and all the functionality is ok except when the buttion is disabled. And note that I have added the line if(counter>1) return; just to test but it stops the button from working the way I wanted (that's why I am not using a flag. I don't want this line to be there when I solve the problem!). The log is enough to inform me that the method is being called when the button is re-enabled, because I clicked it when it was disabled!

Upvotes: 5

Views: 6148

Answers (2)

Daniel Nugent
Daniel Nugent

Reputation: 43322

I found that with your example, the AsyncTask was completing so fast that there was a very short amount of time that the Button was not clickable due to being disabled. So, it's basically a timing issue.

I found that by delaying the re-enabling of the Button by 4 seconds, it works as expected.

Note with this change that visually, the Button is re-enabled a split second after the TextView is populated.

Here is the code change in onPostExecute():

        @Override
        protected void onPostExecute(String result) {
            mTextView.setText(result);

            Handler handler = new Handler();
            handler.postDelayed(new Runnable() {

                @Override
                public void run() {
                    //re-enable the button
                    mButton.setEnabled(true);

                }
            }, 4000);
        }

Note that you can remove the counter logic and it should work as expected now:

public void downloadOnClick(View v) {

    Log.d(this.getLocalClassName(), "Button was clicked");

    ConnectivityManager connMgr = (ConnectivityManager)
            getSystemService(Context.CONNECTIVITY_SERVICE);
    NetworkInfo networkInfo = connMgr.getActiveNetworkInfo();
    if (networkInfo != null && networkInfo.isConnected()) {

        mButton.setEnabled(false);

        mTextView.setText("Getting html file...");
        // if we use simple http, we will need to handle redirect status code
        new DownloadWebpageTask().execute("https://www.google.com/");
    } else {
        mTextView.setText("No network connection available.");
    }
}

Upvotes: 2

antoniodvr
antoniodvr

Reputation: 1259

The error is here:

<Button
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="DOWNLOAD TEXT"
    android:id="@+id/button"
    android:layout_alignParentTop="true"
    android:layout_alignParentLeft="true"
    android:layout_alignParentStart="true"
    android:onClick="downloadOnClick" />

You are missing the concept of OnClickListener! First of all, you have to modify the above xml in this way removing onClick attribute:

<Button
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="DOWNLOAD TEXT"
    android:id="@+id/button"
    android:layout_alignParentTop="true"
    android:layout_alignParentLeft="true"
    android:layout_alignParentStart="true" />

Than you have to modify the Activity onCreate method in order to set the OnClickListener on your button:

protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    mTextView = (TextView) findViewById(R.id.text);
    mButton = (Button) findViewById(R.id.button);
    mButton.setOnClickListener(new View.OnClickListener() {
         public void onClick(View v) {
            counter++;
            Log.d(this.getLocalClassName(), "Button was clicked " + counter + " times.");
            if(counter>1) return;

            ConnectivityManager connMgr = (ConnectivityManager)
                    getSystemService(Context.CONNECTIVITY_SERVICE);
            NetworkInfo networkInfo = connMgr.getActiveNetworkInfo();
            if (networkInfo != null && networkInfo.isConnected()) {
                mButton.setEnabled(false);
                mTextView.setText("Getting html file...");
                // if we use simple http, we will need to handle redirect status code
                new DownloadWebpageTask().execute("https://www.google.com/");
            } else {
                mTextView.setText("No network connection available.");
            }
         }
     });
}

This is the right way to handle a click.

See more:

Moreover:

The best way from my point of view is implements the OnClickListener() on your Activity:

public class MyActivity extends Activity implements View.OnClickListener {
}

In this way you can write for each button where you need to set the OnClickListener do:

buttonX.setOnClickListener(this);
buttonY.setOnClickListener(this);
buttonZ.setOnClickListener(this);

In your Activity onClick() you must override the OnClickListener methods, so:

@Override
public void onClick(View v) {
    if(v.getId() == R.id.ButtonX)){
        //do here what u wanna do.
    } else if(v.getId() == R.id.ButtonY){
        //do here what u wanna do.
    } else if(v.getId() == R.id.ButtonZ){
        //do here what u wanna do.
    }
}

Also in onClick you could use view.getId() to get the resource ID and then use that in a switch/case block to identify each button and perform the relevant action.

Upvotes: 0

Related Questions