Advanced Topic: Creating and Displaying a Custom Dialog
In this lesson we learn how to create and display a more complex dialog using the standard Dialog class.
We need to create a more complex dialog to allow the user to manage the secret key. In this case, we are launching the dialog from the context menu when the user selects "Manage Key."
Note: there is no reason why we cannot show the dialog directly from the Activity simply by calling:
this.showDialog(MANAGE_PASSWORD);
The call to showDialog will call onCreateDialog "the first time" where we can instantiate our custom dialog. Since the alert will maintain its own state on orientation change, it is important not to launch a new alert on each call to onCreate.
Here is what our custom key dialog looks like:
One reason to use onCreateDialog and showDialog is that it lets the Android OS automagically handle the landscape view.
Cool!
Given:
private static final int MANAGE_PASSWORD= 0;
First, we create the XML layout as password_dialog.xml at res/layout.
<?xml version="1.0" encoding="utf-8"?>
<TableLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/TableLayoutPassword"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<TextView
android:text="@string/text_view_manage_password_maintitle"
android:id="@+id/TextViewManagePasswordMainTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
</TextView>
<EditText
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/edit_text_password_first"
android:id="@+id/EditTextPasswordFirst">
</EditText>
<EditText
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/edit_text_password_second"
android:id="@+id/EditTextPasswordSecond">
</EditText>
<TextView
android:text="@string/text_view_manage_password_subtitle"
android:id="@+id/TextViewManagePasswordSubTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
</TextView>
<TableRow>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/ButtonPasswordUpdate"
android:text="@string/button_password_update">
</Button>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/ButtonPasswordCancel"
android:text="@string/button_password_cancel">
</Button>
</TableRow>
</TableLayout>
Then we add the string definitions to string.xml as in:
<string name="edit_text_password_first"></string>
<string name="edit_text_password_second"></string>
<string name="text_view_manage_password_subtitle">Enter New Key Twice</string>
<string name="text_view_manage_password_maintitle">Change Key</string>
<string name="button_password_update">Update Key</string>
<string name="button_password_cancel">Cancel</string>
We then edit the onOptionsItemSelected method to call showDialog as in:
@Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle item selection
switch (item.getItemId()) {
case R.id.manage_password:
this.showDialog(MANAGE_PASSWORD);
return true;
case R.id.options:
;
return true;
case R.id.about:
this.showDialog(DIALOG_ABOUT);
return true;
default:
return super.onOptionsItemSelected(item);
}
}
Next we edit the onCreateDialog method. This method will be called "the first time" following a call to showDialog.
protected Dialog onCreateDialog(int id) {
Dialog dialog;
switch(id) {
case MANAGE_PASSWORD:
dialog= getInstancePasswordDialog();
break;
case DIALOG_ABOUT:
// do the work to define the About Dialog
dialog= getInstanceAlertDialog(); // called "the first time"
break;
default:
dialog = null;
}
return dialog;
}
Now comes the hard part, writing the getInstancePasswordDialog() method. The key call is d.setContentView which implicitly inflates the view. We use anonymous inner classes to handle the CANCEL and UPDATE button click events. Here it is:
private Dialog getInstancePasswordDialog() {
final Dialog d= new Dialog(this);
d.setContentView(R.layout.password_dialog);
d.setTitle("Key Manager");
final EditText editTextPasswordFirst= (EditText)d.findViewById(R.id.EditTextPasswordFirst);
final EditText editTextPasswordSecond= (EditText)d.findViewById(R.id.EditTextPasswordSecond);
// CANCEL BUTTON HANDLER
final Button buttonCancel= (Button)d.findViewById(R.id.ButtonPasswordCancel);
buttonCancel.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
d.dismiss();
}
});
// UPDATE BUTTON HANDLER
final Button buttonUpdate= (Button)d.findViewById(R.id.ButtonPasswordUpdate);
buttonUpdate.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
String entryFirst= editTextPasswordFirst.getText().toString();
String entrySecond= editTextPasswordSecond.getText().toString();
if (entryFirst.equals(entrySecond)) { // do NOT use == with string values
if (entryFirst.length() != lengthPassword) { // invalid key length
editTextPasswordFirst.setText("Invalid Key Length of "+
new Integer(entryFirst.length()).toString());
editTextPasswordSecond.setText("");
editTextPasswordFirst.selectAll();
editTextPasswordFirst.requestFocus();
}
else {
editTextPassword.setText(entryFirst);
// *** Clear Key Fields ***
editTextPasswordFirst.setText("");
editTextPasswordSecond.setText("");
d.dismiss();
}
}
else { // entries do not match
editTextPasswordFirst.setText("Entries did not match!");
editTextPasswordSecond.setText("");
editTextPasswordFirst.selectAll();
editTextPasswordFirst.requestFocus();
}
}
});
return d;
}
Wow. When the user enters keys that do not match or keys that are invalid, the key is not updated.
Possible GOTCHA! R.id.my_new_dialog not found
You add a new dialog, my_new_dialog.xml, to res/layout, but R.id.my_new_dialog does not exist! Well, you need to edit the context menu file to generate the id.
<?xml version="1.0" encoding="utf-8"?>
<menu
xmlns:android="http://schemas.android.com/apk/res/android">
<item android:id="@+id/manage_password"
android:title="@string/manage_password"/>
<item android:id="@+id/my_new_dialog"
android:title="@string/new"/>
<item android:id="@+id/options"
android:title="@string/options"/>
<item android:id="@+id/about"
android:title="@string/about"/>
</menu>
Possible GOTCHA! Back vs Cancel
Here is a strange gotcha. When the user hits the back button and your Dialog is showing, the dialog is canceled. That is right, our CANCEL logic is bypassed. There are at least two possible solutions. First, if you want to trap for the back key when your dialog has the focus, make your app class implements OnCancelListener and trap for the onCancel event. You can duplicate your CANCEL logic here. So our class ConfuseText implements OnCancelListener
public class ConfuseText extends Activity implements OnCancelListener{
Next we move three local (method) variables to instance variables:
private Dialog d;
private EditText editTextPasswordFirst;
private EditText editTextPasswordSecond;
We register our app to listen for onCancel event from the Dialog d:
d.setOnCancelListener(this);
Finally, we trap the onCancel event in our main class, ConfuseText.
//*** Must trap here for users hitting the back button from a dialog ***
// put cancel code for each dialog here also
@Override
public void onCancel(DialogInterface dialog) {
// TODO Auto-generated method stub
if (dialog == d ) { // user hit back button from password dialog
editTextPasswordFirst.setText(""); // should not be null if d is not null
editTextPasswordSecond.setText("");
}
}
The second way to make sure the password text is cleared is to simply initialize the dialog every time it is displayed by providing the optional method onPrepareDialog in ConfuseText as:
protected void onPrepareDialog(int id, Dialog dialog){
switch(id) {
case MANAGE_PASSWORD:
// clear possible visible secret key
editTextPasswordFirst.setText(""); // should not be null
editTextPasswordSecond.setText("");
break;
}
}
Then we can remove the setText code from the password dialog cancel button handler as:
// CANCEL BUTTON HANDLER
// *** Moved initialization code to onPrepareDialog ***
final Button buttonCancel= (Button)d.findViewById(R.id.ButtonPasswordCancel);
buttonCancel.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
//editTextPasswordFirst.setText(""); // moved to onPrepareDialog
//editTextPasswordSecond.setText(""); // moved to onPrepareDialog
d.dismiss();
}
});
So we move the setText code from post-dialog on-cancel text clearance to pre-dialog text clearance. Arrg!
Have fun,
JAL