PCSalt
YouTube GitHub
Back to Android
Android

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 needMethod
Simple message with buttonssetTitle() + setMessage() + setPositiveButton()
Checkbox listsetMultiChoiceItems()
Radio button listsetSingleChoiceItems()
Custom layout (XML)setView(layoutInflater.inflate(...))
Custom layout (code)setView(linearLayout)
Prevent dismiss on outside tapsetCancelable(false)
Style buttonsdialog.show() then dialog.getButton(...)
Override dismiss behaviorSet null listener, override after show()

Download Source Code

View on GitHub krrishnaaaa/create-alertdialog-android View on GitHub krrishnaaaa/custom-alertdialog-programmatically