Monday, June 27, 2011

Android - adding an enhanced dialog


Ok. So, I have a default dialog display which says "Congratulations - you have advanced to the next level". I'm thinking, this is kind of disappointing, ad least for me, when I get it. I'd like to add an image to the message, maybe fill the screen a bit more - make it a bit more of a celebratory event.

I think I saw something about how to do that somewhere. Let's google android dialog.

Here's something:

http://developer.android.com/guide/topics/ui/dialogs.html

AlertDialog
A dialog that can manage zero, one, two, or three buttons, and/or a list of selectable items that can include checkboxes or radio buttons. The AlertDialog is capable of constructing most dialog user interfaces and is the suggested dialog type. See Creating an AlertDialog below.


http://developer.android.com/guide/topics/ui/dialogs.html#AlertDialog

No image there, just displaying yes/no buttons and adding lists and radio buttons.

How about googling "Android Dialog image"


http://www.coderanch.com/t/493136/Android/Mobile/Adding-image-AlertDialog

Here the good fellow recommends:

or the last and easiest option would be to create a custom dialog interface using xml, and use the android api for layout inflating.

http://developer.android.com/guide/topics/ui/dialogs.html#CustomDialog

Ah, yes, that's what I as looking for.

Creating a Custom Dialog


If you want a customized design for a dialog, you can create your own layout for the dialog window with layout and widget elements. After you've defined your layout, pass the root View object or layout resource ID to setContentView(View).


Nice, there's a nice example right there:


For example, to create the dialog shown to the right:

Create an XML layout saved as custom_dialog.xml:

android:id="@+id/layout_root"
android:orientation="horizontal"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:padding="10dp"
>
android:layout_width="wrap_content"
android:layout_height="fill_parent"
android:layout_marginRight="10dp"
/>
android:layout_width="wrap_content"
android:layout_height="fill_parent"
android:textColor="#FFF"
/>



This XML defines an ImageView and a TextView inside a LinearLayout.

Set the above layout as the dialog's content view and define the content for the ImageView and TextView elements:


Context mContext = getApplicationContext();
Dialog dialog = new Dialog(mContext);

dialog.setContentView(R.layout.custom_dialog);
dialog.setTitle("Custom Dialog");

TextView text = (TextView) dialog.findViewById(R.id.text);
text.setText("Hello, this is a custom dialog!");
ImageView image = (ImageView) dialog.findViewById(R.id.image);
image.setImageResource(R.drawable.android);


Nice. Let's create the layout - call it NextLevelDialog. Cool, eclipse even provides an new XML Layout, or the Android add-in, I guess.

Oops, forgot, these files can only have lowercase names. Let's call it next_level.

Ok, where in the code to I all that alert? Somewhere in QuestionActivity, no doubt.

There it is:

appState.incrementQuizStartAndFinish(this);
text = "Congratulations! You have advanced to next level. Press \"Start\" to continue";

Toast.makeText(getApplicationContext(), text, Toast.LENGTH_LONG)
.show();

Ok.

After you instantiate the Dialog, set your custom layout as the dialog's content view with setContentView(int), passing it the layout resource ID. Now that the Dialog has a defined layout, you can capture View objects from the layout with findViewById(int) and modify their content.

That's it. You can now show the dialog as described in Showing A Dialog.

http://developer.android.com/guide/topics/ui/dialogs.html#ShowingADialog

It looks like this ought to do it:

When it's time to show one of the dialogs, call showDialog(int) with the ID of a dialog:

showDialog(DIALOG_PAUSED_ID);

So, here's the code:

appState.incrementQuizStartAndFinish(this);
text = "Congratulations! You have advanced to next level. Press \"Start Quiz\" to continue";

Context mContext = getApplicationContext();
Dialog dialog = new Dialog(mContext);

dialog.setContentView(R.layout.next_level);
dialog.setTitle("Congratulations!");

TextView nextLevelText = (TextView) dialog.findViewById(R.id.next_level_text);
nextLevelText.setText(text);
ImageView image = (ImageView) dialog.findViewById(R.id.image);
image.setImageResource(R.drawable.dialog_custom);
showDialog(R.layout.next_level);



Let's run it...

Nothing. Add back in the Toast display and it shows. Am I not correctly displaying it? Yes, I am not. It's a bit more complicated than the resource identifier.


Showing a Dialog

A dialog is always created and displayed as a part of an Activity.

//k

You should normally create dialogs from within your Activity's onCreateDialog(int) callback method.

//k

When you use this callback, the Android system automatically manages the state of each dialog and hooks them to the Activity, effectively making it the "owner" of each dialog.

//k

As such, each dialog inherits certain properties from the Activity.

//k

For example, when a dialog is open, the Menu key reveals the options menu defined for the Activity and the volume keys modify the audio stream used by the Activity.

// for all dialogs?


When you want to show a dialog, call showDialog(int) and pass it an integer that uniquely identifies the dialog that you want to display.

// that's the ticket.

When a dialog is requested for the first time, Android calls onCreateDialog(int) from your Activity, which is where you should instantiate the Dialog.

// that's where I'm instantiating it.

This callback method is passed the same ID that you passed to showDialog(int). After you create the Dialog, return the object at the end of the method.

// oh. I'm confused. What is the callback method they're referring to? It must be the onCreateDialog(int), which is called when I call showDialog(int). But, after I create the dialog, I'm supposed to return the dialog object at the end of the method. The method I create it in? But, that's just some random method with a void return.

Ah here's a key piece which I initially didn't bother to pull in:

Note: If you decide to create a dialog outside of the onCreateDialog() method, it will not be attached to an Activity. You can, however, attach it to an Activity with setOwnerActivity(Activity).

This means I need to define the dialog in the onCreateMethod. To continue:

Before the dialog is displayed, Android also calls the optional callback method onPrepareDialog(int, Dialog).

// ok

Define this method if you want to change any properties of the dialog each time it is opened.

// I could use this if I want to display random images instead of the same one every
// time.

This method is called every time a dialog is opened, whereas onCreateDialog(int) is only called the very first time a dialog is opened.

// a-ha.

If you don't define onPrepareDialog(), then the dialog will remain the same as it was the previous time it was opened.

// Got it. I don't mind over-explaining things one bit. This is good documentation.

This method is also passed the dialog's ID, along with the Dialog object you created in onCreateDialog().

// ok. Still a bit hazy on how the id plays a role, though.


The best way to define the onCreateDialog(int) and onPrepareDialog(int, Dialog) callback methods is with a switch statement that checks the id parameter that's passed into the method.

// Sound good.

Each case should check for a unique dialog ID and then create and define the respective Dialog. For example, imagine a game that uses two different dialogs: one to indicate that the game has paused and another to indicate that the game is over.

// Right.

First, define an integer ID for each dialog:


static final int DIALOG_PAUSED_ID = 0;
static final int DIALOG_GAMEOVER_ID = 1;


// Ok.

Then, define the onCreateDialog(int) callback with a switch case for each ID:

protected Dialog onCreateDialog(int id) {
Dialog dialog;
switch(id) {
case DIALOG_PAUSED_ID:
// do the work to define the pause Dialog
break;
case DIALOG_GAMEOVER_ID:
// do the work to define the game over Dialog
break;
default:
dialog = null;
}
return dialog;
}

// Ok. I see. It's a mechanism for deciding which dialog to display.

Note: In this example, there's no code inside the case statements because the procedure for defining your Dialog is outside the scope of this section. See the section below about Creating an AlertDialog, offers code suitable for this example.

// Ok.

When it's time to show one of the dialogs, call showDialog(int) with the ID of a dialog:

showDialog(DIALOG_PAUSED_ID);


Ok, this is great. I might even show a different dialog for when they don't get to
next level. Or I might just leave it as a toast. This will be a great
place to put in ads. What the word - inter...something.

First, let's set up a couple of ints:

static final int SAME_LEVEL_ID = 0;
static final int NEXT_LEVEL_ID = 1;

And the method:

protected Dialog onCreateDialog(int id) {
Dialog dialog = null;
switch (id) {
case SAME_LEVEL_ID:
// do the work to define the pause Dialog
break;
case NEXT_LEVEL_ID:
AppState appState = (AppState) this.getApplication();
appState.incrementQuizStartAndFinish(this);
String text = "Congratulations! You have advanced to next level. Press \"Start Quiz\" to continue";

Context mContext = getApplicationContext();
dialog = new Dialog(mContext);

dialog.setContentView(R.layout.next_level);
dialog.setTitle("Congratulations!");

TextView nextLevelText = (TextView) dialog
.findViewById(R.id.next_level_text);
nextLevelText.setText(text);
ImageView image = (ImageView) dialog.findViewById(R.id.image);
image.setImageResource(R.drawable.dialog_custom);
break;
default:
dialog = null;
}
return dialog;
}


And the call:

showDialog(NEXT_LEVEL_ID);

Let's try it again...

Crash. View the log...

W/WindowManager( 96): Attempted to add window with non-application token WindowToken{40705a40 token=null}. Aborting.
E/AndroidRuntime( 3354): FATAL EXCEPTION: main
E/AndroidRuntime( 3354): android.view.WindowManager$BadTokenException: Unable to add window -- token null is not for an app


That's a break from the usual NPE, at least.


Let's google this...

http://stackoverflow.com/questions/1561803/android-progressdialog-show-crashes-with-getapplicationcontext

It's a popular question, 26 upvotes.

The highest rated answer suggest:

dialog = new Dialog(this);

Instead of

Context mContext = getApplicationContext();
dialog = new Dialog(mContext);

Let's try it:

Yes. That was it. It flashed by because I finish immediately afterwards, but I spotted the little green android dude. But, what was the cause of the problem? It was pulling the wrong context. It must be a superclass's context.

Ok. So, how do I keep it up there? Require an answer?

Probably one button which says ok. How do I add it?

Let's go back to the alert documentation. It had the buttons in there.

http://developer.android.com/guide/topics/ui/dialogs.html#AlertDialog

It looks like it's a bit different:

AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setMessage("Are you sure you want to exit?")
.setCancelable(false)
.setPositiveButton("Yes", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
MyActivity.this.finish();
}
})
.setNegativeButton("No", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
dialog.cancel();
}
});
AlertDialog alert = builder.create();


I'm pretty sure there's not setPositive/Negative...

Nope. I'll have to add the button in the layout. Then add a listener. There's no add listener method, but I thought I saw something somewhere with an listener to a custom dialog.

Google custom dialog listener android

It brings me to the dialog doc at http://developer.android.com/reference/android/app/Dialog.html

This is what I'm looking for,

setOnShowListener(DialogInterface.OnShowListener listener)
Sets a listener to be invoked when the dialog is shown.

But I don't see why having a listener will cause the dialog to stay on the screen.

Ok, I'll have to check into this. I'll pick this up later.

No comments:

Post a Comment