Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Updating TextView from Async Task which use custom program dialog

In one of my app, I have a scenario where I need to do some background task. For doing that I am using Async Task. Also I am using custom progress dialog. Below is the layout of the custom progress dialog

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/layout_root"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:layout_gravity="center_vertical|center_horizontal"
    android:orientation="vertical" >

    <ProgressBar
        android:layout_width="60dp"
        android:layout_height="60dp"
        android:indeterminateDrawable="@drawable/progressloader" 
        android:layout_gravity="center"/>

    <TextView
        android:id="@+id/progressMessage"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textColor="@color/black"
        android:textSize="18sp"
        android:text="Please wait...." />

</LinearLayout>

Everything works fine but when I try to set text to TextView then I am getting java NullPointerException.

AsyncTask code

private class InitialSetup extends AsyncTask<String, Integer, Long> {

        ProgressDialog dialog = new ProgressDialog(getParent(),R.style.progressdialog);


        @Override
        protected void onPreExecute() {
            dialog.show();
            dialog.setContentView(R.layout.progressbar);

        }

        @Override
        protected Long doInBackground(String... urls) {
                    //    txtView.setText("Testing");    here I am getting the error
            fetchDetails();

            return 0;
        }

        @Override
        protected void onPostExecute(Long result) {

            if (this.dialog.isShowing()) {
                this.dialog.dismiss();
            }

            populateUI(getApplicationContext());
        }
    }

MainActivity

public class SummaryActivity extends Activity {


final TextView txtView = (TextView)findbyid(R.id.progressMessage);
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.accountsummary);

              new InitialSetup().execute("");

    }
}
like image 449
CrazyCoder Avatar asked Apr 17 '12 16:04

CrazyCoder


4 Answers

If I understand correctly, your TextView of which you want to set the text can be found in the xml file progressbar.xml (i.e. R.layout.progressbar). This TextView can be obtained once the content view has been set (using setContentView()). In your code you set it before this call is been and the code of mussharapp, he is calling it to early. Namely, he calls it after the setContentView(R.layout.accountsummary) call which does not contain the TextView. Consequently, the variable txtView will be NULL and you will get a NullPointerException.

What you should do is the following:

  • Set the variable txtView in onPreExecute, after setContentView is called.
  • Based on Paresh Mayani's explanation: Use the runOnUiThread method.

For the code look down below:

private class InitialSetup extends AsyncTask<String, Integer, Long> {

        ProgressDialog dialog = new ProgressDialog(getParent(),R.style.progressdialog);
        // The variable is moved here, we only need it here while displaying the
        // progress dialog.
        TextView txtView;

        @Override
        protected void onPreExecute() {
            dialog.show();
            dialog.setContentView(R.layout.progressbar);
            // Set the variable txtView here, after setContentView on the dialog
            // has been called! use dialog.findViewById().
            txtView = dialog.findViewById(R.id.progressMessage); 
        }

        @Override
        protected Long doInBackground(String... urls) {
            // Already suggested by Paresh Mayani:
            // Use the runOnUiThread method.
            // See his explanation.
            runOnUiThread(new Runnable() {
               @Override
               public void run() {
                  txtView.setText("Testing");       
               }
            });

            fetchDetails();
            return 0;
        }

        @Override
        protected void onPostExecute(Long result) {

            if (this.dialog.isShowing()) {
                this.dialog.dismiss();
            }

            populateUI(getApplicationContext());
        }
    }
like image 178
dennisg Avatar answered Oct 23 '22 18:10

dennisg


Yes, because you are trying to set the TextView inside the doInBackground() method, and this is not allowed,

Why not allowed? Because There is a only one Thread running which is UI Main Thread, and it doesn't allowed to update UI from thread process. read more info here: Painless Threading

So there is a solution if you want to set the TextView inside the doInBackground() method, do the UI updating operations inside the runOnUiThread method.

Otherwise, suggestion is to do all the UI display/update related operations inside the onPostExecute() method instead of doInBackground() method of your AsyncTask class.

like image 25
Paresh Mayani Avatar answered Oct 23 '22 17:10

Paresh Mayani


(TextView)findViewByid(R.id.progressMessage);

should only be executed after the command setContentView().

TextView txtView;

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.accountsummary);
    **txtView = (TextView)findbyid(R.id.progressMessage);**


    new InitialSetup().execute("");

}

Also you can only change UI elements in the main UI thread. doInBackground() is not in the main UI thread. Make UI changes in onPostExecute

public class InitialSetup extends AsyncTask<String, Integer, Long> {

        private Activity activity;
        ProgressDialog progressDialog;

        public InitialSetup(Activity activity) {
            this.activity = activity;
        }




        @Override
        protected void onPreExecute() {
            progressDialog = new ProgressDialog(activity);
            progressDialog.setMessage("Starting task....");
            progressDialog.show();    
        }

        @Override
        protected Long doInBackground(String... urls) {
            // do something

            //        

            return 0;
        }

        @Override
        protected void onPostExecute(Long result) {
            progressDialog.dismiss();
             //Perform all UI changes here
            **textView.setText("Text#2");**
        }
    }
like image 1
Mark Pazon Avatar answered Oct 23 '22 17:10

Mark Pazon


The explanations are correct: You are not to make UI changes in any thread except the thread which create the UI. But AsyncTask has a method called

onProgressUpdate()

which always will run in the UI Thread. So based on the modifications by dennisg your code should look like this:

private class InitialSetup extends AsyncTask<String, String, Long> {

    ProgressDialog dialog = new ProgressDialog(getParent(),R.style.progressdialog);
    // The variable is moved here, we only need it here while displaying the
    // progress dialog.
    TextView txtView;

    @Override
    protected void onPreExecute() {
        dialog.show();
        dialog.setContentView(R.layout.progressbar);
        // Set the variable txtView here, after setContentView on the dialog
        // has been called! use dialog.findViewById().
        txtView = dialog.findViewById(R.id.progressMessage); 
    }

    @Override
    protected Long doInBackground(String... urls) {
        publishProgress("Testing");

        fetchDetails();

        return 0;
    }

    @Override
    protected void onPostExecute(Long result) {

        if (this.dialog.isShowing()) {
            this.dialog.dismiss();
        }

        populateUI(getApplicationContext());
    }

    @Override
    protected void onProgressUpdate(String... update) {
        if (update.length > 0)
            txtView.setText(update[0]); 
    }
}

Note that the type of the parameter of onProgressUpdate is the second type given in AsyncTask!

Extra: To make your code more robust you should check if the progress dialog still exists before setting the text.

like image 1
ToBe_HH Avatar answered Oct 23 '22 16:10

ToBe_HH