Customizing Dialogs with DialogFragment

We have looked at dialogs before where a dialog box pops up and floats over the UI (that is, partially obscuring the current UI) in a transparent manner prompting the user for input needed by the program -- e.g., date, time. We use dialogs quite a lot in the MyRuns set of labs. Android supports DialogFragment, which is a fragment that displays a dialog window, floating on top of its activity's window. In what follows we show how DialogFragment can be used to construct and customize dialogs. You can use this knowledge for your MyRuns lab2 and future labs.

What this lecture will teach you

Resources

DialogFragment tutorial from the Android developers site.

Check out the demo project

Dialogs

In this lecture, we will first show how the DialogFragment can be extended and customized. Android supports three ways to use dialogs:

  1. Use the Dialog class and its numerous extensions; such as AlertDialog, DatePickerDialog. There are a large number of canned dialog boxes that you can use depending on the sort of user interaction you need. For your own custom dialogs, you could extend the Dialog base class, as do DatePickerDialog and TimePickerDialog.

  2. You can apply the dialog theme directly by the activity giving the feel of a dialog box.

  3. Toast.

In this class, we will be focusing on DialogFragment. Your implementation should override onCreateDialog(Bundle) to create a custom Dialog object, with its own content. As shown in the code below, once your Dialog object (called "ret" in this example) is set up to satisfy your needs, it will be returned from the onCreateDialog() callback for the dialog box to be shown to the user.

override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
lateinit var ret: Dialog
...
...
return ret
}

AlertDialog

To create the Dialog object, we use AlertDialog.Builder, a builder class used to construct a custom dialog. In the code below, requireActivity()returns the hosting activity of the DialogFragment. If the activity is null, the system throws an exception.

val builder = AlertDialog.Builder(requireActivity()) 

Once done you can then set up the various parts of the dialog as discussed above -- title, assign buttons programmatically, text to be displayed, and importantly, event listeners to handle user interaction such as text input, touching buttons, making a selection. In the simple code example below, we inflate the layout of the dialog in a way similar to inflating a fragment. The UI of the dialog window is setup in the fragment_my_dialog.xml. We created an "ok" button using setPositiveButton and a "cancel" button using setNegativeButton. The parameter "this" in these methods is a DialogInterface.OnClickListener object, which handles the button click events. The reason we can use "this" here is because we implement the listener as a part of our custom dialog fragment (e.g., class MyDialog : DialogFragment(), DialogInterface.OnClickListener{...}). Lastly, we call builder.create()to create the custom Dialog.

val view: View = requireActivity().layoutInflater.inflate(R.layout.fragment_my_dialog, null)
builder.setView(view)
builder.setTitle("my title")
builder.setPositiveButton("ok", this)
builder.setNegativeButton("cancel", this)
ret = builder.create()

The complete code for the onCreateDialog callback is shown below. Note that if you have multiple different dialog designs, you can use an if statement to navigate among different designs.  For the sake of simplicity, we only create one. You will be creating more in MyRuns. Here the idea is to use an integer to indicate the need for rendering different dialog designs. When we start the DialogFragment in an Activity, we pass that integer as argument using a Bundle. In the code below, we retrieve the Bundle in the argument using val bundle = arguments.

override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
lateinit var ret: Dialog
val bundle = arguments
val dialogId = bundle!!.getInt(DIALOG_KEY)
if (dialogId == TEST_DIALOG) {
val builder = AlertDialog.Builder(requireActivity())
val view: View = requireActivity().layoutInflater.inflate(R.layout.fragment_my_dialog, null)
builder.setView(view)
builder.setTitle("my title")
builder.setPositiveButton("ok", this)
builder.setNegativeButton("cancel", this)
ret = builder.create()
}
return ret
}

The code snippet below, we create a custom DialogFragment object, setup arguments, and show the dialog box by calling show() on the DialogFragment object. 

val myDialog = MyDialog()
val bundle = Bundle()
bundle.putInt(MyDialog.DIALOG_KEY, MyDialog.TEST_DIALOG)
myDialog.arguments = bundle
myDialog.show(supportFragmentManager, "my dialog")

In general, the AlertDialog can be used to construct a common set of use cases:

  1. Asking the user for input with a number of buttons (OK, cancel, save, etc.).
  2. Presenting a list of options using check boxes or radio buttons
  3. Presenting a text entry box for user input.