Lecture 6 UI Components

This lecture discusses how to include menus and pop-up dialogs in an Android application as additional navigation and display components. Note that this lecture aims to provide exposure rather than depth to these concepts; for further details and options, see the official Android documentation.

This lecture references code found at https://github.com/info448-s17/lecture06-menus-dialogs.

6.1 The Action Bar

Let’s start one of the most prominent visual components in the default app: the App Bar or Action Bar. This acts as the sort of “header” for your app, providing a dedicated space for navigation and interaction (e.g., through menus). The ActionBar21 is a specific type of Toolbar that is most frequenly used as the App Bar, offering a particular “look and feel” common to Android applications.

While the AppCompatActivity used throughout this course automatically provides an Action Bar for the app, it is also possible to add it directly (such as if you are using a different Activity subclass). To add your own Action Bar, you specify a theme that does not include an ActionBar, and then include an <android.support.v7.window.Toolbar> element inside your layout wherever you want the toolbar to go. See Setting up the App Bar for details. This will also allow you to put the Toolbar anywhere in the application’s layout (e.g., if you want it to be stuck to the bottom).

  • To see this in action, change the android:theme attribute of the <application> element in the Manifest to "@style/Theme.AppCompat.Light.NoActionBar". We’ll discuss this process in more detail when we talk about Themes and Styles.

From in the Activity’s Java code, we can get access to the Action Bar by calling the getSupportActionBar() method (for a support Toolbar). We can then call utility methods on this object to interact with it; for example .hide() will hide the toolbar!

6.3 Dialogs

While it is simple enough to make menu items that log out some text, logs cannot be seen the user. Instead, we woud like to show the message to the user as a kind of “pop-up” message.

A Dialog24 is a “pop-up” modal (a view which doesn’t fill the screen) that either asks the user to make a decision or provides some additional information. At it’s most basic, Dialogs are similar to the window.alert() function used in JavaScript.

There is a base Dialog class, but almost always we use a pre-defined subclass instead (similar to how we’ve use AppCompatActivity). AlertDialog25 is the most common version: a simple message with buttons you can respond with (confirm, cancel, etc).

We don’t actually instantiate an AlertDialog directly (in fact, it’s constructors are protected so inaccessible to us). Instead we use a helper factory class called an AlertDialog.Builder. There are a number of steps to use a builder to create a Dialog:

  1. Instantiate a new builder for this particular dialog. The constructor takes in a Context under which to create the Dialog. Note that once the builder is initialized, you can create and recreate the same dialog with a single method call—that’s the benefits of using a factory.
  2. Call “setter” methods on the builder in orer to specify the title, message, etc. for the dialog that will appear. This can be hard-coded text or a reference to an XML String resource (as a user-facing String, the later is more appropriate for published applications). Each setter method will return a reference to the builder, making it easy to chain them.
  3. Use appropriate setter methods to specify callbacks (via a DialogInterface.OnClickListener) for individual buttons. Note that the “positive” button normally has the text "OK", but this can be customized.
  4. Finally, actually instantiate the AlertDialog with the builder.create() method, using the show() method to make the dialog appear on the screen!
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle("Alert!")
       .setMessage("Danger Will Robinson!");
builder.setPositiveButton("I see it!", new DialogInterface.OnClickListener() {
  public void onClick(DialogInterface dialog, int id) {
    // User clicked OK button
  }
});

AlertDialog dialog = builder.create();
dialog.show();

An important part of learning to develop Android applications is being able to read the API to discover effective options. For example, can you read the AlertDialog.Builder API and determine how to add a “cancel” button to the alert?

While AlertDialog is the most common Dialog, Android supports other subclasses as well. For example, DatePickerDialog and TimePickerDialog provide pre-defined user interfaces for picking a date or a time respectively. See the Pickers guide for details about how to utilize these.

6.3.1 DialogFragments

The process described above will create and show a Dialog, but that dialog has a few problems in how it interacts with the rest of the Android framework—namely with the lifecycle of the Activity in which it is embedded.

For example, if the device changes configurations (e.g., is rotated from portrait to landscape) then the Activity is destroyed and re-created (it’s onCreate() method will be called again). But if this happens while a Dialog is being shown, then a android.view.WindowLeaked error will be displayed and the Dialog is lost!

To avoid these problems, we need to have a way of giving that Dialog its own lifecycle which can interact with the the Activity’s lifecycle… sort of like making it a modular piece of an Activity… that’s right, we need to make it a Fragment! Specifically, we will use a subclass of Fragment called DialogFragment, which is a Fragment that displays as a modal dialog floating above the Activity (no extra work needed).

Just like with the Fragment examples from the previous lecture, we’ll need to create our own subclass of DialogFragment. It’s often easiest to make this a nested class if the Dialog won’t be doing a lot of work (e.g., shows a simple confirmation).

Rathern than specifying a Fragment layout through onCreateView(), we can instead override the onCreateDialog() callback to specify a Dialog object that will provide the view hierarchy for the Fragment. This Dialog can be created with the AlertDialog.Builder class as before!

public static class MyDialogFragment extends DialogFragment {

    public static HelloDialogFragment newInstance() {
        Bundle args = new Bundle();
        HelloDialogFragment fragment = new HelloDialogFragment();
        fragment.setArguments(args);
        return fragment;
    }

    public Dialog onCreateDialog(Bundle savedInstanceState) {
        AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
        //...
        AlertDialog dialog = builder.create();
        return dialog;
    }
}

Finally, we can actually show this DialogFragment by instantiating it (remember to use a newInstance() factory method!) and then calling the show() method on it to make it show as a Dialog. The show() method takes in a FragmentManager used to manage this transaction. By using a DialogFragment, it is possible to change the device configuration (rotate the phone) and the Dialog is retained.

Here’s the other neat trick: a DialogFragment is just a Fragment. That means we can use it anywhere we normally used Fragments… including embedding them into layouts! For example if you made the MoviesFragment subclass DialogFragment instead of Fragment, it would be able to be used in the exact same as before. It’s still a Fragment, just with extra features—one of which is a show() method that will show it as a Dialog!

  • Use setStyle(DialogFragment.STYLE_NO_TITLE, android.R.style.Theme_Holo_Light_Dialog) to make the Fragment look a little more like a dialog.

The truth is that Dialogs are not very commonly used in Android (compare to other GU systems). Apps are more likely to just dynamically change the Fragment or Activity being shown, rather than interrupt the user flow by creating a pop-up modal. And 80% of the Dialogs that are used are AlertDialogs. Nevertheless, it is worth being familiar with this process and the patterns it draws upon!

6.4 Toasts

Dialogs are a powerful way of providing messages and information to users, but they are pretty “heavy” in terms of both their interaction (they stop all other interaction to show the user a message) and the effort required to implement them. Sometimes you just want a “pop-up” message that isn’t quite as prominent and doesn’t require the user to click “okay” once they’ve seen it.

A simple, quick way of giving some short visual feedback is to use what is called a Toast. This is a tiny little text box that pops up at the bottom of the screen for a moment to quickly display a message.

  • It’s called a “Toast” because it pops up!

Toasts are pretty simple to implement, as with the following example (from the official documentation):

Context context = this; //getApplicationContext(); //use application context to avoid disappearing if Activity is closed quickly.
String text = "Hello toast!";
int duration = Toast.LENGTH_SHORT;

//use factory method instead of constructor
Toast toast = Toast.makeText(context, text, duration);
toast.show();

But since this Activity is a Context, and we can just use the Toast anonymously, we can shorten this to a one-liner:

Toast.makeText(this, "Hello toast!", Toast.LENGTH_SHORT).show();

Boom, a quick visual alert method we can use for proof-of-concept stuff!

Toasts are intended to be a way to provide information to the user (e.g., giving them quick feedback), but they can possibly be useful for testing too (though in the end, Logcat is going to be your best bet for debugging, especially when trying to solve crashes or see more complex output).