Dialogs

Intro 

A Dialog is a special window for displaying messages or prompting the user for some kind of action, such as confirming to delete data. The Toast class can be described as a special kind of a dialog that displays a single message for a fixed amount of time. When a dialog appears on the screen, the dialog gains the focus and the underlying activity loses the focus. All user interactions are directed toward the dialog. The focus returns to the activity when the dialog disappears from the screen.

Android supports four types of dialogs. They are: AlertDialog , DatePickerDialogTimePickerDialog, and ProgressDialog. The latter three are specialized dialogs for showing the date picker widget, the time picker widget, and the progress bar.  The ProgressDialog is no longer recommended for use by Google.  Instead, they recommend you avoid using it, and instead use a ProgressBar in your Activity's layout. 

The AlertDialog  class is the most general of the four because you can customize it in several very flexible ways. If none of the predefined dialogs fit your needs, then you can create one-of-the-kind dialog using the Dialog class. All four predefined dialogs are subclasses of this Dialog class.

For this lesson, use the following sample Android Studio project, referencing the code and modifying as you go along to ensure you understand precisely what's happening:

Download Dialog Sample Android Studio Project

Dialog Fragment

While not strictly required, it is highly recommended that Dialogs are implemented by using the DialogFragment class.  Doing so will allow your Dialog to handle lifecycle events like screen rotation automatically.  If you were to include a Dialog without using a DialogFragment, you would have to write all necessary code to ensure that you app handles these events gracefully.

To use a DialogFragment, simply create you own Java Class that extends the DialogFragment class:

public class BasicAlertDialogFragment extends DialogFragment { ... }

After extending DialogFragment, your Class need only implement one method, onCreateDialog(), which will contain the details for the Dialog that will be displayed:

@Override
public Dialog onCreateDialog(Bundle savedInstanceState) { ... }

Finally, to actually show the Dialog, in your Activity you create an instance of your custom DialogFragment and call the show() method on it, passing the Fragment Manager that will handle creating the Dialog, and a tag to uniquely identify the dialog:

BasicAlertDialogFragment dialog = new BasicAlertDialogFragment();
dialog.show(getFragmentManager(), "BasicAlertDialogFragment");

All of the Dialogs we'll create in this lesson will use a DialogFragment as recommended by Google.

IMPORTANT: In order to enable Fragment, your Activity must extend the FragmentActivity class, as opposed to Activity.  Extending ActionBarActivity works as well, since it is a subclass of FragmentActivity.

A Basic Alert Dialog

Our first example dialog illustrates the most basic use of the AlertDialog class. The dialog displays a window with an icon, a title, a message, and three buttons.

We place a basic Button on the screen, and when it is clicked, we make this dialog appear.  You can easily adjust this so a dialog appears in response to a menu selection, error condition, or some other event.

An Android alert dialog can include up to three buttons. Although it can contain no buttons, most common uses of an alert dialog will have at least one button. The icon, the title, and the message text are all optional.

The easiest and the most common way to create an alert dialog is by using the AlertDialog.Builder class. Here's how you construct the sample dialog:

@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {

    AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());

    builder.setTitle("Basic Alert Dialog Title");
    builder.setIcon(R.drawable.cs_logo);
    builder.setMessage("Hello. This is the message body.");
    builder.setPositiveButton("Pos", this);
    builder.setNeutralButton("Neu", this);
    builder.setNegativeButton("Neg", this);

    return builder.create();
}

It is a two-step process. First, you create an AlertDialog.builder instance. You tell this builder all the properties you want for your dialog. After you've finished specifying the properties, you ask the builder to create the specified alert dialog.

In this example, we're setting all three buttons. If you specify only one button (it doesn't matter which one), you will see it positioned at the center of the dialog. If you specify two buttons (again, it doesn't matter which two), then you will see them sized and distributed equally.

Every method of the AlertDialog class returns itself (this), so we can chain the dot notations as a shortcut (you see this style of coding in many sample programs on the Internet):

builder.setTitle("Alert Dialog Title")
  .setIcon(R.drawable.icon)
  .setMessage("Hello. This is the message body.")
  .setPositiveButton("Pos", this)
  .setNeutralButton("Neu", this)
  .setNegativeButton("Neg", this)
  .setCancelable(false);

By default, an alert dialog is cancelable, i.e., the user can dismiss the dialog by clicking the Back button. For common uses, however, we do not want the dialog to be dismissed without selecting one of the buttons on the dialog, so we set it not to be cancelable.

To process the button click events of an alert dialog, our DialogFragment needs to implement the DialogInterface.OnClickListener.  

public class BasicAlertDialogFragment extends DialogFragment 
                                      implements DialogInterface.OnClickListener { ...

This will also require you to implement an onClick method that receives two parameters: the dialog and the button id. Here's the onClick method of the sample program:

public void onClick(DialogInterface dialog, int id) {
    String buttonName = "";
    switch (id) {
        case Dialog.BUTTON_NEGATIVE: buttonName = "Negative"; break;
        case Dialog.BUTTON_NEUTRAL: buttonName = "Neutral"; break;
        case Dialog.BUTTON_POSITIVE: buttonName = "Positive"; break;
    }
    Log.d("PEPIN", "You clicked the " + buttonName + " button");
}

We need to check the first parameter if this listener is registered to more than one dialog.  Beware...this is NOT the same OnClickListener we used with our Views.  Be sure you're using the right one!

Alert Dialogs With Lists

There are three ways to display a list of items in a dialog: List, Radio Buttons and Checkboxes.  They all use the setItems() method call on the dialog builder:

builder.setItems(arrayName, this);

 

List Dialog

The simplest of the list-style alert dialogs displays a simple list of selectable items. When you click an item, the dialog closes immediately so we won't include any buttons on this dialog. 

// array of items to be displayed in dialog list
String[] choice = new String[ ] {"One", "Two", "Three", "Four", "Five", "Six"};

@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {

    AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());

    builder.setTitle("List Dialog: Select One")
            .setItems(choice, this); // set list here

    return builder.create();
}

public void onClick(DialogInterface dialog, int itemId) {

    Log.d("PEPIN", "You selected item " + itemId);
}

 

Radio Button Dialog

The second dialog displays a list of items with radio buttons. It's created the same way as the list dialog above, except that it includes the additional builder attribute setSingleChoiceItems().  

String[] choice = new String[ ] {"One", "Two", "Three", "Four", "Five", "Six"};

@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {

    AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());

    builder.setTitle("Radio Button Dialog: Select One")
            .setSingleChoiceItems(choice, -1, this)
            .setPositiveButton("OK", this)
            .setNegativeButton("Cancel", this);
    return builder.create();
}

public void onClick(DialogInterface dialog, int itemId) {
    Log.d("PEPIN", "You selected item " + itemId);
}

The first argument to the setSingleChoiceItems is an array of strings, the second argument the index position of the preselected item (-1 mean no item is pre-selected), and the last argument the listener. When an item in a list is clicked, the DialogInterface.OnClickListener's onClick method is called. We can determine which item was clicked by its index position. The list is zero-based so the first time is 0, the second item is 1, and so forth. 

 

Check Box Dialog

The final list-style dialog uses setMultiChoiceItems instead of setSingleChoiceItems, enabling the user to select more than one item at at time, respectively, to assign selectable items in the list. Here's how the radio button alert dialog is created:

String[] choice = new String[ ] {"One", "Two", "Three", "Four", "Five", "Six"};

@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {

    AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());

    builder.setTitle("Checkbox Dialog: Select Many")
            .setMultiChoiceItems(choice, null, this)
            .setPositiveButton("OK", this)
            .setNegativeButton("Cancel", this);

    return builder.create();
}

// Single Button Listener
public void onClick(DialogInterface dialog, int id) {

    String buttonName = "";

    switch (id) {
        case Dialog.BUTTON_NEGATIVE: buttonName = "Negative"; break;
        case Dialog.BUTTON_POSITIVE: buttonName = "Positive"; break;
    }

    Log.d("PEPIN", "You clicked the " + buttonName + " button");
}

// MultiChoice Item Listener
@Override
public void onClick(DialogInterface dialog, int itemId, boolean isChecked) {

    Log.d("PEPIN", "Checkbox " + itemId + " " + isChecked);
}

If the list includes the checkboxes, then we must register a DialogInterface.MultiChoiceClickListener instance. Every time an item in the checkbox list is clicked (going from the checked to unchecked state and vice versa), the MultiChoiceClickListener's onClick method is called. The third parameter of the onClick method is a boolean value that indicates the item is checked (true) or unchecked (false).

Also, you may have noticed that in the check box dialogs example, we have two onClick methods, as opposed to the single onClick in the other Dialogs.  That is because in addition to capturing the checkbox clicks, we still need to capture the button clicks just like in the basic alert dialog.  

Notice that the two onClick methods have different parameters.  Don't get them confused!

Custom Alert Dialog

The content of our final Alert dialog is custom-made. For this example, we include a single EditText widget. To build a custom alert dialog, we need to define a layout for the content. Inside the program, we use a layout inflater (similar to the menu inflater we used earlier in this guide) to extract the layout for the dialog content and pass this layout to the setView method of the dialog builder. Here's how:

EditText inputET;

@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {

    AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());

    LayoutInflater inflater = LayoutInflater.from(getActivity());

    View layout = inflater.inflate(R.layout.custom_dialog, null);
    inputET = (EditText) layout.findViewById(R.id.inputET);

    builder.setView(layout);
    builder.setTitle("Custom Dialog: Enter Data")
            .setPositiveButton("OK", this)
            .setNegativeButton("Cancel", this);
    return builder.create();
}

public void onClick(DialogInterface dialog, int itemId) {

    if (itemId == Dialog.BUTTON_POSITIVE) {
        Log.d("PEPIN", "You entered " + inputET.getText());
    } else {
        Log.d("PEPIN", "You cancelled your input");
    }

    inputET.setText("");
}

Date and Time Picker Dialogs

This is your first opportunity to see the DatePicker and TimePicker widgets. They can be used within your main layout like other Views, or as part of a Dialog.  The DatePickerDialog and TimePickerDialog classes, as you can guess from their names, are custom dialogs for displaying the date and time picker widgets, along with the title and two buttons.

 

Android 5 and 4 Date Pickers:

Android 5 and 4 Time Pickers:

 

The sample date and time picker dialogs are created in the following manner:

Calendar c = Calendar.getInstance(); //current date and time 

dateDlg = new DatePickerDialog(this, this,
  c.get(Calendar.YEAR),
  c.get(Calendar.MONTH),
  c.get(Calendar.DAY_OF_MONTH));

timeDlg = new TimePickerDialog(this, this,
  c.get(Calendar.HOUR_OF_DAY),
  c.get(Calendar.MINUTE),
  false);

The first argument is the Context instance and the second argument the event listener. We are passing an instance of the Main activity here. The last argument for the TimePickerDialog constructor is the designation for 12-hour or 24-hour display format. You pass true for the 24-hour format and false for the 12-hour AM/PM format.

The event listeners for the two classes are, respectively,  DatePickerDialog.OnDateSetListener and TimePickerDialog.OnTimeSetListener. The corresponding methods are onDateSet and onTimeSet. These methods are called when the Set button is clicked. The following methods illustrate how the date and time information are retrieved:

public void onDateSet(DatePicker view, int year,
    int monthOfYear, int dayOfMonth) {
    Toast.makeText(this, "Date Selected: " + (monthOfYear+1) + "/" +
        dayOfMonth + "/" + year, Toast.LENGTH_SHORT).show();
}

public void onTimeSet(TimePicker view, int hourOfDay, int minute) {
    Toast.makeText(this, "Time Selected: " + hourOfDay + ":" + minute,
        Toast.LENGTH_SHORT).show();
}

When you open the same time picker dialog or the date picker dialog for the second time, the time and date set previously are retained. If you want to always open them with the current time and date, then you get the current time and date from a calendar and call the updateTime and updateDate. Then call the show method. This approach is better than creating a new instance of the TimePickerDialog or the DatePickerDialog every time you want a time or date picker dialog.

Date and Time Formatting

If you want to display the date and time in a more conventional format such as December 25, 2011 or 02:50 PM instead of 1/12/2011 or 14:50, then you can use the SimpleDateFormat class from java.text package. Here's an example for date formatting:

SimpleDateFormat sdf = new SimpleDateFormat("MMMM dd, yyyy");
Calendar cal = new GregorianCalendar(year, monthOfYear, dayOfMonth);
Toast.makeText(this, "Date Selected: " + sdf.format(cal.getTime()),
    Toast.LENGTH_SHORT).show();

You create an instance of the GregorianCalendar class (from the java.util package) for a specific date (Calendar.getInstance( ) creates a calendar instance of the current date only). You pass the desired format when creating an instance of the SimpleDateFormat class. The format method of the class returns a string in the designated format. You pass a Date instance to the format method. The getTime method of the calendar returns a Date instance. If you want to display the current date, then you can write it as

sdf.format(new Date());

Here's an example for time formatting:

SimpleDateFormat sdf = new SimpleDateFormat("hh:mm a"); 
Calendar cal = new GregorianCalendar(); 
cal.set(Calendar.HOUR, hourOfDay);
cal.set(Calendar.MINUTE, minute);
Toast.makeText(this, "Time Selected: " + sdf.format(cal.getTime()),
    Toast.LENGTH_SHORT).show();

We use the same SimpleDataFormat class for both date and time formatting (you can in fact include both date and time in a single format statement). There's no constructor for creating a GregorianCalendar by passing just the hour and minute, so we create a default GregorianCalendar instance and then assign the respective values using the set method.

You only need to create a SimpleDateFormat instance once. Don't recreate an instance every time you need to format. Just create it once and call the format method every time you need to format the date or time information. You need to create a separate SimpleDateFormat instance for each format style you want.

For a table of common styles, check this out: http://docs.oracle.com/javase/6/docs/api/java/text/SimpleDateFormat.html

Custom Listeners

If you've been paying close attention up to this point, you may have noticed something interesting: all of the OnClickListeners are implemented by the DialogFragment subclasses.  The result is that every action we take when an item or button is clicked occurs within the scope of the DialogFragment.  This is fine as long as the things I want to do can be handled by the dialog, such as creating LogCat entries, showing Toasts, saving data to file, Launching an Activity, etc.  But what if I need to return a value to the Parent Activity?  In that case, you may need to create your own Listener.  This provides us an excellent opportunity to practics some good object oriented programming!

Open the CustomDialogListenerFragment Class in the sample code and we'll walk through it as an example of a working solution to our problem. 

 

In order to accomplish our task, we'll need to define an interface, our "Listener", which will be implemented by the Activity.  In the Activity, we'll define the methods required of our custome listener interface.  Finally, in the DialogFragment class, we'll set our dialog to notify the custom listener when a behavior occurs.  If you're having a hard time visualizing this, maybe a UML diagram will help:

Activity as Dialog

If you want, you can make it so that an Activity in your app will display as if it were a Dialog.  The simplest way to achieve this effect is to add tell the Activity to use a valid Dialog style in the app's manifest file:

android:theme="@android:style/Theme.Dialog"

It's important to note that when you do this, your Activity MUST extend the Activity class, and NOT ActionBarActivity (or any other subclass of Activity that relies on the AppCompat library).

The reverse can also be accomplished, configuring a Dialog to display full screen to emulate an Activity.  I'll leave that as an exercise to the reader (http://developer.android.com/guide/topics/ui/dialogs.html#FullscreenDialog).

Progress Dialogs

CAUTION:  Progress Dialogs are deprecated!  You can still make and use them, but modern user interfaces avoid them in favor if something like a ProgressBar in your layout.  This portion of the lesson is for educational purposes only, and should not be used for production apps.

 

This dialog includes a progress bar along with other optional dialog components such as the title, message, icon, and so forth. There are two types of animation: a spinning wheel and a horizontal bar. A spinning wheel is used for showing the progress of indeterminate amount, and a horizontal bar is used for showing the state of progress of determinate amount. The spinning wheel keeps rotating continually, repeating the same animation, while the horizontal bar gets updated during the process to show the percentage of completion (i.e., initially the bar is all gray, but as the task is getting being completed, the left portion of the bar turns to orange to indicate the amount of completion). Using either type of the progress dialog requires the use of a thread, but the spinner wheel version is much simpler. We will show the use of the spinning wheel progress dialog here and discuss the horizontal bar progress dialog in a separate handout on multithreaded programming.

Similar to other dialog sample applications, we'll place a button on the screen. When the button is clicked, a progress dialog appears.

This dialog is programmed to disappear after 5 seconds. We are simulating that our hypothetical task will take 5 seconds to complete.

 The most basic progress dialog is created by calling the show class method of ProgressDialog with the context, the title, and the message as the arguments:

ProgressDialog progressDlg;
. . .
progressDlg = ProgressDialog.show(this, "My Progress Dialog", "Running...");

The show method creates and displays a progress dialog. The method also returns a reference to the progress dialog. We assign it to the variable progressDlg , so we have a reference to a dialog to dismiss it after 5 seconds. After the dialog is displayed, we start a new thread. In the thread's run method, we put a 5-second pause before calling the dialog's cancel method to make the dialog disappear.

The thread management is implemented as follows:

MyThread thread = new MyThread();
thread.start();
. . .
class MyThread extends Thread { //Inner class of Main
    public void run( ) {
        try {
            Thread.sleep(5000); //pause for 5 seconds
        } catch (InterruptedException e) { }
            progressDlg.cancel();
        }
}

 Instead of using the Thread class directly, it is often more convenient and flexible to use the Timer  and TimerTask  classes. Here's how we can achieve the same result using these two classes:

Timer timer = new Timer();
timer.schedule(new MyTimerTask(), 5000); //5 sec delay before calling
//the run method
. . .
class MyTimerTask extends TimerTask {
    public void run( ) {
        progressDlg.cancel();
    }
}

 Instead of the class method shown in the sample project, we can create a ProgressDialog instance using the standard new operator and calling the instance methods, for example:

progressDlg = new ProgressDialog(this);
progressDlg.setIcon(R.drawable.icon);
progressDlg.setTitle("My Progress Dialog");
progressDlg.setMessage("Running...");
progressDlg.setCancelable(true); //allow the user to cancel it

We need to create a new instance for each display of the progress dialog. If you reuse the same progress dialog instance, the animation of the spinning wheel happens only for the first time. Displaying it from the second time onward, the progress bar is inactive. 

Tasks

1. Modify the Basic Alert Dialog to have only two buttons, "Submit" and "Cancel". 

2. Add the code required so that when the user clicks on the "OK" button of the Custom dialog, the text entered in the dialog's EditText field is displayed in the MainActivity message area.

3. Modify the DatePicker Dialog such that when it returns a value, the TimePicker dialog is automatically opened.  When the TimePicker Dialog returns, the complete Date and Time is displayed in the message area.

Question: What state is the parent Activity in when a Dialog is visible?  Modify the project to verify your answer!

External References

For more information on Dialogs, see the Android Developer documents: 

Google Dialogs Tutorial

[Credits: Some portions of this lesson courtesy of Dr Tom Otani, Professor, Naval PostGraduate School]