AlertDialog in Android — The Complete Guide
Everything you need to know about AlertDialog in Android — basic usage, selection dialogs, custom layouts, and button customization. All in Kotlin.
This guide covers everything you need to know about AlertDialog in Android — from basic usage to custom layouts. All examples are in Kotlin.
Basic AlertDialog
The simplest AlertDialog has a title, a message, and buttons. Android supports three button types:
- Positive — confirms an action (e.g., “OK”, “Save”)
- Negative — cancels or dismisses (e.g., “Cancel”)
- Neutral — an alternative action (e.g., “Later”, “Learn More”)
AlertDialog.Builder(this)
.setTitle("Confirm Delete")
.setMessage("Are you sure you want to delete this item?")
.setPositiveButton("Delete") { _, _ ->
// handle delete
}
.setNegativeButton("Cancel") { dialog, _ ->
dialog.dismiss()
}
.show()
You can also add an icon next to the title:
AlertDialog.Builder(this)
.setTitle("Warning")
.setMessage("This action cannot be undone.")
.setIcon(R.drawable.ic_warning)
.setPositiveButton("OK", null)
.show()
To prevent the dialog from being dismissed when the user taps outside or presses back:
AlertDialog.Builder(this)
.setTitle("Important")
.setMessage("You must accept the terms to continue.")
.setCancelable(false)
.setPositiveButton("Accept") { _, _ ->
// proceed
}
.show()
Selection Dialogs
Multi-choice (Checkboxes)
Use setMultiChoiceItems() when the user can select multiple options.
val items = arrayOf("Kotlin", "Java", "Python", "Go")
val checkedItems = booleanArrayOf(false, false, false, false)
val selectedItems = mutableListOf<String>()
AlertDialog.Builder(this)
.setTitle("Select Languages")
.setMultiChoiceItems(items, checkedItems) { _, index, isChecked ->
if (isChecked) {
selectedItems.add(items[index])
} else {
selectedItems.remove(items[index])
}
}
.setPositiveButton("Done") { _, _ ->
// use selectedItems
}
.setNegativeButton("Cancel", null)
.show()
The checkedItems array tracks the state of each checkbox. If you reopen the dialog, previously selected items stay checked.
Single-choice (Radio Buttons)
Use setSingleChoiceItems() when only one option can be selected.
val themes = arrayOf("Light", "Dark", "System Default")
var selectedIndex = 0
AlertDialog.Builder(this)
.setTitle("Choose Theme")
.setSingleChoiceItems(themes, selectedIndex) { _, index ->
selectedIndex = index
}
.setPositiveButton("Apply") { _, _ ->
// apply themes[selectedIndex]
}
.setNegativeButton("Cancel", null)
.show()
The second parameter of setSingleChoiceItems() is the initially selected index. Pass -1 if nothing should be pre-selected.
Note: Don’t use setMessage() together with choice items — the message takes priority and the list won’t show.
Custom Layout from XML
For anything beyond buttons and lists, create a custom XML layout and inflate it into the dialog.
The layout file
Create res/layout/dialog_login.xml:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="24dp">
<com.google.android.material.textfield.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Username"
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/etUsername"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="text" />
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:hint="Password"
app:passwordToggleEnabled="true"
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/etPassword"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="textPassword" />
</com.google.android.material.textfield.TextInputLayout>
</LinearLayout>
Inflating and using it
val dialogView = layoutInflater.inflate(R.layout.dialog_login, null)
val etUsername = dialogView.findViewById<TextInputEditText>(R.id.etUsername)
val etPassword = dialogView.findViewById<TextInputEditText>(R.id.etPassword)
AlertDialog.Builder(this)
.setTitle("Login")
.setView(dialogView)
.setCancelable(false)
.setPositiveButton("Login") { _, _ ->
val username = etUsername.text.toString()
val password = etPassword.text.toString()
// handle login
}
.setNegativeButton("Cancel", null)
.show()
The key method is setView() — it takes any View and places it between the title and the buttons.
Custom Layout Programmatically
Sometimes you don’t want to create a separate XML file for a simple custom layout. You can build views in code:
val layout = LinearLayout(this).apply {
orientation = LinearLayout.VERTICAL
setPadding(48, 32, 48, 16)
}
val etName = EditText(this).apply {
hint = "Enter your name"
inputType = InputType.TYPE_CLASS_TEXT
isSingleLine = true
}
val etEmail = EditText(this).apply {
hint = "Enter your email"
inputType = InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS
isSingleLine = true
}
layout.addView(etName)
layout.addView(etEmail)
AlertDialog.Builder(this)
.setTitle("Contact Info")
.setView(layout)
.setPositiveButton("Save") { _, _ ->
val name = etName.text.toString()
val email = etEmail.text.toString()
// handle save
}
.setNegativeButton("Cancel", null)
.show()
This approach works for simple layouts. For anything with multiple nested views, Material components, or constraints — use XML. It’s easier to read and maintain.
Customizing Buttons
Changing button colors
You can customize button appearance, but there’s a catch — you must call show() first, then access the buttons. Calling getButton() before show() returns null.
val dialog = AlertDialog.Builder(this)
.setTitle("Delete Account")
.setMessage("This will permanently delete your account and all data.")
.setPositiveButton("Delete", null)
.setNegativeButton("Cancel", null)
.create()
dialog.show()
dialog.getButton(DialogInterface.BUTTON_POSITIVE).apply {
setTextColor(ContextCompat.getColor(context, android.R.color.holo_red_dark))
}
dialog.getButton(DialogInterface.BUTTON_NEGATIVE).apply {
setTextColor(ContextCompat.getColor(context, android.R.color.darker_gray))
}
Overriding button click behavior
By default, clicking any button dismisses the dialog. To prevent this — for example, to validate input before dismissing — set the click listener after show():
val dialogView = layoutInflater.inflate(R.layout.dialog_login, null)
val etUsername = dialogView.findViewById<TextInputEditText>(R.id.etUsername)
val etPassword = dialogView.findViewById<TextInputEditText>(R.id.etPassword)
val dialog = AlertDialog.Builder(this)
.setTitle("Login")
.setView(dialogView)
.setPositiveButton("Login", null) // null — we'll override this
.setNegativeButton("Cancel", null)
.create()
dialog.show()
dialog.getButton(DialogInterface.BUTTON_POSITIVE).setOnClickListener {
val username = etUsername.text.toString()
val password = etPassword.text.toString()
if (username.isBlank() || password.isBlank()) {
// don't dismiss — show error
if (username.isBlank()) etUsername.error = "Username required"
if (password.isBlank()) etPassword.error = "Password required"
} else {
// valid input — proceed and dismiss
handleLogin(username, password)
dialog.dismiss()
}
}
The trick is passing null as the click listener in setPositiveButton(), then setting a custom OnClickListener after show(). This gives you full control over when the dialog dismisses.
Quick Reference
| What you need | Method |
|---|---|
| Simple message with buttons | setTitle() + setMessage() + setPositiveButton() |
| Checkbox list | setMultiChoiceItems() |
| Radio button list | setSingleChoiceItems() |
| Custom layout (XML) | setView(layoutInflater.inflate(...)) |
| Custom layout (code) | setView(linearLayout) |
| Prevent dismiss on outside tap | setCancelable(false) |
| Style buttons | dialog.show() then dialog.getButton(...) |
| Override dismiss behavior | Set null listener, override after show() |