Alert Dialogs are common for validating forms. Consider the following example
if (validate(field1) == false)
{
//indicate that formatting is not valid through an alert dialog
showAlert("What you have entered in field1 doesn't match required format");
//set focus to the field
//..and continue
}
This is frequently done in javascript through "alert" function which looks like
alert("Hello! this is an alert")
And javascript will pop open a synchronous dialog that shows this message to the user and displays an OK button that would be pressed by the user. On return the flow of the program continues.
Support for alert dialogs in Android
As it turns out there is no such direct function called "showAlert" that is readily available. Instead Android supports a general purpose facility for working with a number of alert dialogs. These features are made available through an alert dialog builder as defined by the class:
android.app.AlertDialog.Builder
The supported set of alert dialogs include
Yes/No message dialog
Yes/No Long Message Dialog
Pick One from a List Dialog
Pick a number of items from a larger set
Progress Dialog
Single choice from a set of choices dialog
A prompt dialog
The approach to build any of these dialogs is similar.
- Construct a Builder Object
- Set a number of parameters such as number of buttons, list of items etc
- Tell the Builder to build the dialong
- Depending on what is set on the builder object an appropriate dialog is built
Here is how this approach is used to create a general purpose "showAlert" like dialog.
public class Alerts
{
public static void showAlert(String message, Context ctx)
{
//get a builder and set the view
AlertDialog.Builder builder = new AlertDialog.Builder(ctx);
builder.setTitle("Alert Window");
//add buttons and listener
PromptListener pl = new EmptyListener();
builder.setPositiveButton("OK", pl);
//get the dialog
AlertDialog ad = builder.create();
//show
ad.show();
}
}
public class EmptyListener
implements android.content.DialogInterface.OnClickListener
{
public void onClick(DialogInterface v, int buttonId)
{
}
}
Encouraged by this success let's see if we can take on another staple dialog in javascript called the "prompt" dialog where the user is prompted with a hint and the user enters some text into an edit box. The prompt dialog will return that string to the program to continue.
This is a good example because it features a number of facilities provided by the Builder class. So let us consider the code where we build up the prompt dialog and show. Once complete, the intention is to use code something similar to this:
public void OnDeleteFile()
{
String filename = Alerts.prompt("Enter filename string", this);
if (filename == null) {
//cancel has been clicked
//nothing to do
//return
}
else {
//filename is available
//
deletefile(filename);
}
}
So with no further ado, let's implement the "prompt" static function
public class Alerts
{
public static String prompt(String message, Context ctx)
{
//load some kind of a view
LayoutInflater li = LayoutInflater.from(ctx);
View view = li.inflate(R.layout.promptdialog, null);
//get a builder and set the view
AlertDialog.Builder builder = new AlertDialog.Builder(ctx);
builder.setTitle("Prompt");
builder.setView(view);
//add buttons and listener
PromptListener pl = new PromptListener(view,ctx);
builder.setPositiveButton("OK", pl);
builder.setNegativeButton("Cancel", pl);
//get the dialog
AlertDialog ad = builder.create();
//show
ad.show();
return pl.prompt;
}
}
Now let us consider how the listener is designed to make this happen
public class PromptListener
implements android.content.DialogInterface.OnClickListener
{
String promptValue = null;
View promptDialogView = null;
public PromptListener(View inDialogView)
{
promptDialogView = inDialogView;
}
public void onClick(DialogInterface v, int buttonId)
{
if (buttonId == DialogInterface.BUTTON1)
{
//ok button
promptValue = getPromptText();
}
else
{
//cancel button
promptValue = null;
}
}
private String getPromptText()
{
EditText et =
(EditText)
promptDialogView.findViewById(R.id.promptEditTextControlId);
return et.getText().toString();
}
}
To understand the views used by this prompt dialog let us take a look at the view XML definition required by the following code
LayoutInflater li = LayoutInflater.from(ctx);
View view = li.inflate(R.layout.promptdialog, null);
The corresponding xml file will be
\res\layout\promptdialog.xml
Contents of that file would be
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:id="@+id/prompt_message"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:layout_marginLeft="20dip"
android:layout_marginRight="20dip"
android:text="@string/alert_dialog_username"
android:gravity="left"
android:textAppearance="?android:attr/textAppearanceMedium" />
<EditText
android:id="@+id/prompt_edit_text_control_id"
android:layout_height="wrap_content"
android:layout_width="fill_parent"
android:layout_marginLeft="20dip"
android:layout_marginRight="20dip"
android:scrollHorizontally="true"
android:autoText="false"
android:capitalize="none"
android:gravity="fill_horizontal"
android:textAppearance="?android:attr/textAppearanceMedium" />
</LinearLayout>
However after writing all this code you will notice that the "prompt" dialog always returns null even if the user is entering text into the prompt dialog. As it turns out, in the following code
ad.show() //dialog.show
return pl.getPrompt(); // listener.getprompt
The show will invoke the dialog asynchronously. This means the "promptListener" gets called for the prompt value before user had time to enter and click OK.
I have taken you on this false ride (that I took my self early on) to demonstrate you an important limitation of the architecture of dialogs in Android. With that introduction to the oddity of Android dialogs, let us look into the philosophy behind "asynchronous" modal dialogs in Android.
What you must know about dialogs in Android
Displaying or showing dialogs in Android is an asynchronous process. Once a dialog is shown the main thread that invoked the dialog returns and continues to process the rest of the code. Although "modality" relationship between the activity that invoked the dialog and the dialog itself is maintained, the parent activity is forced to return to its message pump.
The "modality" is maintained because the focus is drawn to the dialog box and user won't be able to click on the parent activity while the dialog is on top.
The semantics of "modal" dialogs in some windowing systems suggest that user must provide a response before the parent window can continue. The parent thread appears to be blocking for the caller. Android supports the former and not the later. Android seemed to have taken this route because the designers of Android wanted the parent window or activity to continue to process other non-ui events such as life cycle events. If the thread were to block this would not be possible.
The implication of this model is that you cannot have a simple dialog where you can ask for a response and wait for it before moving on. In fact your programming model for dialogs have to be completely different basing on call backs.
Rearchitecting the "prompt" dialog for reuse
So you can't have dialogs that return stuff to the caller directly. Given that "truth" how can we implement something like a "prompt" dialog functionality in a reasonable way. This can only be done with call backs. What follows is an implementation that tries to stay as open and as generic as possible so that one can use it for any number of prompts from the same activity and at the same time allows multiple actions to be invoked for each prompt.
Let us start by examining how an activity that makes use of prompt dialogs could look like
public Class MyActivity extends Activity
implements IStringPromptCallBack
{
public static int PROMPT_ACTION_1;
public static int PROMPT_ACTION_2;
public void exercisePrompt()
{
Alert.prompt(this, //callback for the dialog
,"Please enter data for xyz.." // message to prompt
,this //This context for dialog builder
,PROMPT_ACTION_1 ) // to distinguish between multiple prompts
}
//callbacks allowing multiple callback actions
public void promptCallBack(String promptedString,
int buttonPressed, //which button was pressed
int actionId) //To support multiple prompts
{
if (buttonPressed == IStringPrompterCallBack.OK_BUTTON)
{
..do something with promptedString
}
else
{
..deal with cancel
}
}
}
Let's look at how IStringPromptCallBack looks like
public interface IStringPrompterCallBack
{
public static int OK_BUTTON = -1;
public static int CANCEL_BUTTON = -2;
public void promptCallBack(String promptedString,
int buttonPressed, //which button was pressed
int actionId); //To support multiple prompts
}
Given this let's redesign the "prompt" method on Alerts static class that was introduced earlier
public class Alert
{
public static void Prompt(IStringPrompterCallBack cb
,String message
,Context ctx
,int actionId)
{
LayoutInflater li = LayoutInflater.from(ctx);
View view = li.inflate(R.layout.promptdialog, null);
AlertDialog.Builder builder = new AlertDialog.Builder(ctx);
builder.setTitle("Prompt");
builder.setView(view);
PromptListener pl = new PromptListener(view,ctx,cb,actionId);
builder.setPositiveButton("OK", pl);
builder.setNegativeButton("Cancel", pl);
AlertDialog ad = builder.create();
ad.show();
}
}
Notice how the "prompt" method is now a "void" method and returns nothing. The implementation will become clear by noticing what the PromptListener implemenation will look like:
public class PromptListener
implements android.content.DialogInterface.OnClickListener
{
private IStringPrompterCallBack cb = null;
private int actionId = 0;
private View promptView;
public PromptListener(View view
, Context ctx
, IStringPrompterCallBack inCb
, int inActionId)
{
promptView = view;
cb = inCb;
actionId = inActionId;
}
public void onClick(DialogInterface v, int buttonId)
{
if (buttonId == DialogInterface.BUTTON1)
{
//ok button
String promptValue = getEnteredText();
cb.promptCallBack(promptValue
,IStringPrompterCallBack.OK_BUTTON
,actionId);
}
else
{
String promptValue = getEnteredText();
cb.promptCallBack(null
,IStringPrompterCallBack.CANCEL_BUTTON
,actionId);
}
}
private String getEnteredText()
{
EditText et =
(EditText)
promptView.findViewById(R.id.editText_prompt);
String enteredText = et.getText().toString();
Log.d("xx",enteredText);
return enteredText;
}
}
Managed Dialogs: The recommended way
Although it is reasonable to design dialogs this way, Android recommends a considerably different approach for implementing dialogs. In the recommended approach, Android discourages creating new dialogs in response to actions instead would like to reuse dialog instances that were created before. Android follows a managed protocol to make this scheme work.
Let me describe the protocol first before giving somne examples.
MyActivity
{
//Give unique ids for each type of dialog
//that you may want to use
public static int DIALOG_1=1;
..
@override
protected Dialog onCreateDialog(int dialogId){
switch(dialogId) {
case DIALOG_1:
createDialog1();
}
}
Dialog createDialog1()
{
//use Builder to create the dialog you want
//register call backs
}
private void respondToSomeAction()
{
//asynchronous call
showDialog(DIALOG_1);
}
private void someCallBackFunctions()
{
//have the dialog call you back
}
@Override
protected void onPrepareDialog(int id, Dialog dialog) {
switch (id) {
case DIALOG_1:
//..set any dynamic values
}
}
}//eof-class
To understand the above protocol lets' start with the following code
private void respondToSomeAction()
{
//asynchronous call
showDialog(DIALOG_1);
}
This part of code is calling showDialog with an id in response to some action in the activity class. The action could be a menu action or a button click or any other. As part of this method you need to tell Android a number indicating a dialog type. Android will take this number and see if the dialog identified by this number is already in cache. If it is available it will call the onPrepareDialog for this dialog and then shows the dialog in an asynchronous thread. If the dialog doesn't exist it will call the "onCreateDialog()" with the dialog id passed. It is upto the "onCreateDialog()" function to take that id and create an appropriate dialog. Once this dialog is created by onCreateDialog it will be cached and onCreateDialog won't be used for this id again for the life of that activity.
As already have been emphasized, due to the asynchronous nature of the dialogs the activity should also reserve some methods for call backs to handle when the dialog is dismissed due to any of its buttons being clicked.
Reimplementing the alert dialog as a managed dialog
Let us take the protocol above and see if we can reimplement the previous non managed alert dialog as a managed alert dialog. Let us start by defining a unique ID for this dialog in the context of a given activity
//unique dialog id
private static final int DIALOG_ALERT_ID = 1;
//a counter to demostrate onPrepareDialog
private int alertDemoCounter=0;
In OnCreateDialog make a call to create the alert dialog.
@Override
protected Dialog onCreateDialog(int id) {
switch (id) {
case DIALOG_ALERT_ID:
return createAlertDialog();
}
return null;
}
//set this
private String dynamicMessage = "Initial Message";
private Dialog createAlertDialog()
{
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle("Alert");
builder.setMessage(dynamicMessage);
EmptyOnClickListener l = new EmptyOnClickListener();
builder.setPositiveButton("Ok", l );
AlertDialog ad = builder.create();
return ad;
}
The createALertDialog uses the previou empty on click listener. Notice how the message string is named a "dynamicMessage" string to demonstrate that every time we display a message it is a new message. To accomodate this, code has a private variable called "alertDemoCounter".
The following code uses the onPrepareDialog to set this dynamic message in the alert dialog.
@Override
protected void onPrepareDialog(int id, Dialog dialog) {
switch (id) {
case DIALOG_ALERT_ID:
prepareAlertDialog(dialog);
}
}
private void prepareAlertDialog(Dialog d)
{
AlertDialog ad = (AlertDialog)d;
ad.setMessage(dynamicMessage);
}
Now that we have set the dialog up, we can proceed to use it. Because the dialog is a managed dialog, we have to set the value of the dynamic string every time this dialog is called.
private void exerciseAlertDialog()
{
this.dynamicMessage="Dynamic Message:" + alertDemoCounter++;
showDialog(this.DIALOG_ALERT_ID);
}
Simplifying the dialog protocol
If you have noticed working with these alert dialogs can become quite messy and polutes the main line of code. It is possible to abstract out this protocol in an abstraction that would allows us the following
@Override
protected Dialog onCreateDialog(int id)
{
return this.dialogRegistry.create(id);
}
@Override
protected void onPrepareDialog(int id, Dialog dialog)
{
this.dialogRegistry.prepare(dialog, id);
}
I am going to rewrite the alert dialog one more time with this new abstraction. Under this new abstraction I only need to add the following code to the above to make it work:
//Get an ID for your dialog
private static final int DIALOG_ALERT_ID_3 = 3;
//Create a DialogRegistry to hold dialog builders
private DialogRegistry dr = new DialogRegistry();
//Create your dialog outside of the code
private GenericManagedAlertDialog gmad =
new GenericManagedAlertDialog(this,"InitialValue");
//Register your dialogs with registry in
//activity creation
private void registerDialogs()
{
dr.registerDialog(this.DIALOG_ALERT_ID_3, gmad);
}
//Show the dialog with prepare capability
private void exerciseDialogRegistry()
{
gmad.setAlertMessage("some message:" + this.alertDemoCounter++);
showDialog(this.DIALOG_ALERT_ID_3);
}
The magic becomes clear when you see the code for the following classes
public interface IDialogProtocol
{
public Dialog create();
public void prepare(Dialog dialog);
}
public class GenericManagedAlertDialog implements IDialogProtocol
{
private String alertMessage = null;
private Context ctx = null;
public GenericManagedAlertDialog(Context inCtx, String initialMessage)
{
alertMessage = initialMessage;
ctx = inCtx;
}
public Dialog create()
{
AlertDialog.Builder builder = new AlertDialog.Builder(ctx);
builder.setTitle("Alert");
builder.setMessage(alertMessage);
EmptyOnClickListener l = new EmptyOnClickListener();
builder.setPositiveButton("Ok", l );
AlertDialog ad = builder.create();
return ad;
}
public void prepare(Dialog dialog)
{
AlertDialog ad = (AlertDialog)dialog;
ad.setMessage(alertMessage);
}
public void setAlertMessage(String inAlertMessage)
{
alertMessage = inAlertMessage;
}
}
public class DialogRegistry
{
SparseArray idsToDialogs
= new SparseArray();
public void registerDialog(int id, IDialogProtocol dialog)
{
idsToDialogs.put(id,dialog);
}
public Dialog create(int id)
{
IDialogProtocol dp = idsToDialogs.get(id);
if (dp == null) return null;
return dp.create();
}
public void prepare(Dialog dialog, int id)
{
IDialogProtocol dp = idsToDialogs.get(id);
dp.prepare(dialog);
}
}
References
satya - Monday, February 14, 2011 9:46:17 AM
You can download a sample project from Book2 on this topic here
You can download a sample project from Book2 on this topic here
look for a file named ch05_sampledialogs.zip
satya - Monday, February 14, 2011 9:48:14 AM
Or you can download our sample code from Book 3
Or you can download our sample code from Book 3
look for a file named ProAndroid3_ch08_sampledialogs.zip