Home Android & Kotlin Tutorials

Visual Feedback: Dialogs, Snackbars and Toasts

Providing visual feedback to the user is very important. In this tutorial, you’ll learn about Dialogs, Snackbars, and Toasts.

5/5 3 Ratings

Version

  • Kotlin 1.4, Android 4.1, Android Studio 4.2

Have you ever tapped a button and wondered “did that work?” or “is this doing anything?” while staring at an unchanging screen? Your mobile app is a product that users interact with. Back and forth feedback is crucial to achieving the desired functionality. The app must be told exactly what action the user wants to perform (through their interactions with the interface), and the user needs confirmation that the desired action has been performed. Providing feedback helps build trust, increases user confidence and makes your apps easier to use.

There are several ways to help build this confidence, such as using a simple touch state to show the user the app has registered their tap on a button, a progress indicator to assure the user that something is happening behind the scene or a confirmation message to confirm that an action has completed. Individually, these are relatively small things you can add to your app that, when combined, provide a better, less frustrating experience for users.

In this tutorial, you’ll learn how to:

  • Use a variety of Dialogs to display different types of information
  • Show touch states and Progress Indicators to reassure users the app is working
  • Provide quick updates to users with Snackbars and Toasts
Note: This tutorial assumes a basic knowledge of Kotlin, Android and Android Studio. If you’re new to Android development or haven’t installed Android Studio, refer to these Beginning Android Development tutorials.

Getting Started

Start by downloading the starter project, using the Download Materials button at the top or bottom of this tutorial.

Open the project in Android Studio and get familiar with the files. Then build and run it on a device to see how the app looks.

The screen for providing visual feedback with a list of fruits.

Providing Visual Feedback in Fruit Salad app

The app has a single screen. There are three cards aligned vertically, one for each of the fruits: banana, orange and lime. Each displays an image beside the name of the fruit and the quantity added. The fourth card is actually a button that contains the words “Mystery fruit”. Later, you’ll learn how to display a dialog with an extra fruit item when the user taps this button.

Underneath the cards are three buttons. Initially, these buttons don’t do anything, but throughout this tutorial, you’ll add the following functionality to them.

Tapping Add fruit displays a dialog for adding fruit. The quantity amount is updated on the corresponding fruit card, and a Snackbar confirms which fruit was added and gives the user the option to undo if they picked the wrong fruit by accident.

Tapping Copy list saves a list of the fruits and their quantities to the clipboard on the device. A Toast gives confirmation feedback to the user.

Tapping Clear list resets all the fruit quantities to zero. Because this is an irreversible action, a dialog prompts the user to confirm they’re happy to proceed.

That might sound like a lot to fit into one screen, but each of these dialogs and notifications appears only temporarily. They take up a small area of the screen, yet their effect is mighty!

Note: The sample project for this tutorial has already been set up to use Material Components. To use them in your own app, add the Material Components for Android dependency to your project gradle file. Look at the Material Components repo on GitHub for detailed steps.

Android robot building a wall of blocks (which represent Android components).

Touch States

Often when you press a physical button, you feel it move with the force of your finger and hear a click when it catches on. These are little reassurances that the button has been fully pressed and is working as expected. When making a digital product, you need to make considerations for providing the same kind of feedback.

In Android, the standard way to do this is to provide a touch state on anything the user can interact with. This means that while the user is touching the button, the app confirms it has registered the touch by changing the background color. When the user releases their finger, the color returns to normal, or sometimes a ripple effect is produced. Keep in mind that touch states come pre-built into Material Components.

Build and run. Now, tap the cards and buttons to notice this behavior.

An animation that shows the background of the fourth button changes when it is clicked.

The buttons change color with a ripple effect when you touch them, as does the Mystery fruit view. However, you won’t see the color change on the three fruit cards because they aren’t defined as buttons.

In the project, open activity_main.xml and look at the MaterialCardView attributes.

Screenshot with a list of MaterialCardView attributes.

The card with ID card_mystery has two extra attributes defined: clickable and focusable. This tells the component that the card will handle clicks, so the touch state is automatically added.

Dialogs

Dialogs are small, versatile windows overlaid above the main content on the screen. They can be useful for getting the required input from users or making the user aware of important events. However, they block the rest of the content and can disrupt or take over the attention of the user. Therefore, dialogs should be used sparingly and intentionally. In some cases, it might be better to consider redesigning to use overflow menus or a Snackbar, which you’ll learn how to do shortly.

Various types of dialogs cater to different use cases. The Material Components library provides support for three main types:

  • Simple Dialog: Displays a title and list of items. This dialog doesn’t have any buttons. Instead, the action is triggered when an item is selected.
  • Confirmation Dialog: Similar in design to the simple dialog with the addition up to two buttons: positive and neutral. This type of dialog has the option of multiple item selection and does not trigger an action until the user presses the positive button.
  • Alert Dialog: Has optional title, message and up to three buttons: positive, negative and neutral. This dialog is used when the app requires the user to make a decision or confirm that they have read important information.
Note: The Material Components library also provides MaterialTimePicker and MaterialDatePicker classes for creating specific Picker type Dialogs.

In addition to the Material Components dialog implementations, you can add your own custom layout to a dialog using DialogFragment.

Using MaterialAlertDialogBuilder

The MaterialAlertDialogBuilder provides several functions for setting up a dialog. The builder creates an Android AlertDialog using any defined color or shape in the current app theme, as long as the app uses a Material theme. Use this builder to create any of the dialogs defined in the Material Components library.

The builder allows you to chain methods together. The most commonly used methods allow you to add a title, message and buttons with click listeners. Keep in mind that not all methods can be used together in the same dialog. For instance, it is not possible to have a dialog displaying both a message and a list of selectable items using this builder. You can see the full list of available methods in the official documentation.

To use the builder, you need to import it first. Add the following import statement to the top of MainActivity.kt:

import com.google.android.material.dialog.MaterialAlertDialogBuilder

Simple Dialog

Now, you’re going to display a Simple Dialog showing the three fruit options when the user taps Add fruit. This dialog allows selecting one item. In the project, open MainActivity.kt and find a click listener for the button with ID button_add.

The code containing a click listener for the button with ID button_add.

You’ll see that the tap on Add fruit executes showAddFruitDialog(). Replace TODO in this function with the following code:

MaterialAlertDialogBuilder(this)
   .setTitle(resources.getString(R.string.dialog_add_fruit_title))
   .setItems(fruitItems) { dialog, selectedFruitItem ->
     updateFruitQuantity(selectedFruitItem, true)
     dialog.dismiss()
     showSnackbar(selectedFruitItem)
   }
   .show()

This creates an instance of MaterialAlertDialogBuilder and sets a title for the dialog from the string resources. It populates setItems() with the list of fruits stored in the fruitItems value, and within the lambda it defines the click listener for when a dialog item is tapped. Here, the quantity of the selected item is updated, then the dialog is dismissed. The last line of the click listener calls showSnackbar(selectedFruitItem). This function has not yet been implemented, but you’ll address it later in the tutorial. Finally, show() displays the newly created dialog.

Build and run. Tap Add fruit to see how the Simple Dialog looks and select a fruit item. The corresponding quantity value increases as expected and the dialog is dismissed. You can also dismiss the dialog without updating a fruit item by tapping outside of the dialog.

Animation where dialog pops up and the user selects the first item in a dialog list.

When building your own app, if you want to disable dismissing a dialog without selecting one of the items in a list, chain setCancelable(false) on the dialog builder.

Confirmation Dialog

The Simple Dialog works well for quickly adding a single item, but if you want to allow users to add more than one type of fruit at a time, a Confirmation Dialog is a better fit. The Confirmation Dialog can be configured to allow multiple item selection. It also requires the user to confirm their selection before performing the action.

Now, you’ll implement the Confirmation Dialog. Find showAddFruitDialog() and add the following line at the top:

val checkedItems = booleanArrayOf(false, false, false)

This line creates an array of booleans to hold the state of each fruit item. Initially, they’ll all be unselected.

Then, replace the following line:

.setItems(fruitItems) { dialog, selectedFruitItem ->
     updateFruitQuantity(selectedFruitItem, true)
     dialog.dismiss()
     showSnackbar(selectedFruitItem)
   }

with these lines:

.setNeutralButton(resources.getString(R.string.dialog_cancel)) { dialog, _ ->
      dialog.cancel()
    }
.setPositiveButton(resources.getString(R.string.dialog_add_fruit_positive_button))
    { dialog, _ ->
      checkedItems.forEachIndexed { fruitItem, isChecked ->
        if (isChecked) updateFruitQuantity(fruitItem, true)
      }
      dialog.dismiss()
    }
.setMultiChoiceItems(fruitItems, checkedItems) { _, position, checked ->
      checkedItems[position] = checked
    }

You used the existing MaterialAlertDialogBuilder to create a dialog, and now, besides a title, the builder sets two buttons:

  • A neutral button, which cancels the dialog when tapped
  • A positive button with a click listener that loops through the checked items, updates the fruit quantity if it was selected, and then dismisses the dialog

setMultiChoiceItems() takes the lists of items and states to display the options inside the dialog. When implementing this in your own app, if you want to restrict item selection to one item at a time, replace this method with setSingleChoiceItems().

The click listener here updates the checkedItems array with the new checked state of the item. This keeps the list of states up to date for when the positive button is pressed.

Build and run. Again, tap Add fruit, and this time see how the Confirmation Dialog looks and behaves.

Animation with the Confirmation Dialog.

Alert Dialog

Now that you have a dialog allowing the user to add fruit, you need to provide a way for them to clear the list, too. Clearing the list will be an irreversible action, so you need to add a safeguard to prevent the user from doing this accidentally. An Alert Dialog is perfect for this use case because it enables you to display some extra information about the action’s consequences. The user can then either confirm that they wish to proceed or back out before it’s too late.

As before, in MainActivity.kt, find the button with ID button_clear, which already has a click listener set. On tap, it calls showClearListConfirmationDialog().

The code of the empty showClearListConfirmationDialog method with one TODO.

Replace TODO in this function with the following:

MaterialAlertDialogBuilder(this)
   .setTitle(resources.getString(R.string.dialog_clear_list_title))
   .setMessage(resources.getString(R.string.dialog_clear_list_message))
   .setNeutralButton(resources.getString(R.string.dialog_cancel)) { dialog, _ ->
     dialog.cancel()
   }
   .setNegativeButton(resources.getString(R.string.dialog_negative_button)) { dialog, _ ->
     dialog.dismiss()
   }
   .setPositiveButton(resources.getString(R.string.dialog_clear_list_positive_button
)) { dialog, _ ->
     updateFruitQuantity(null, false)
     dialog.dismiss()
   }
   .show()

Again, you’re using the MaterialAlertDialogBuilder to create and show the dialog. It has a title, message, and neutral, negative and positive buttons. The positive button has a click listener that clears the fruit quantities before dismissing the dialog. It’s good practice to give the positive button a label that describes the action, if possible, to increase clarity and ease of use.

Both the neutral and negative button click listeners close the dialog. dialog.cancel() and dialog.dismiss() do very similar actions, but each can have a different listener attached, so they might have different behaviors according to how you chose to implement them. The most common practice is that dialog.dismiss() is called when the dialog finishes its job and removes it from the screen, and dialog.cancel() is called when the user performs an action to close the dialog.

Build and run. Tap Clear list to display the dialog.

Animation with the Alert Dialog.

Custom Dialog

Sometimes, you’ll want to show a more unique or complex dialog to your users. By creating a custom dialog, you can define your own layout to display in the main content area of the dialog.

Android robot generating a custom layout with the heart-eyes emoji.

To create the Custom Dialog, open dialog_fruit.xml. This simple layout contains a TextView and an ImageView.

The TextView and ImageView in dialog_fruit.xml.

To implement the custom layout in a dialog, you need to extend DialogFragment() and override onCreateDialog(). In the project, open CustomFruitDialog.kt. You’ll see already declared onCreateDialog() which isn’t doing anything custom right now. But that’s about to change. Replace:

super.onCreateDialog(savedInstanceState)

with the following code:

activity?.let {
 val inflater = it.layoutInflater
 AlertDialog.Builder(it)
     .setView(inflater.inflate(R.layout.dialog_fruit, null))
     .setPositiveButton(R.string.dialog_fruit_close) { _, _ ->
       listener?.onDialogButtonClicked()
     }
     .create()
} ?: throw IllegalStateException("Activity cannot be null")

This code first checks that DialogFragment has Activity into which it can be inflated, and throws an error if Activity is null. In this case, the app wouldn’t be able to display the dialog on the screen.

This time, the code uses the Android AlertDialog.Builder class directly to create the custom dialog. setView() inflates the custom layout into the dialog, and setPositiveButton() makes use of the standard dialog buttons. You aren’t required to use the dialog buttons if your custom layout has its own built in.

The positive button’s click listener calls onDialogButtonClicked() from a custom interface, which has not yet been defined.

The code of the custom interface called Listener.

In the same file, find the Listener interface and add the following code in place of TODO:

fun onDialogButtonClicked()

This is an interface method you can implement in any class where you wish to show this custom dialog. In this way, you can change the behavior of the positive button depending on the context of where the dialog is shown.

Showing the Custom Dialog

Now, you need to show the dialog. Go back to MainActivity.kt and find the card with ID card_mystery. This card already has a click listener assigned that calls loadSurpriseDialog(), which has not yet been implemented.

The code with the empty loadSurpriseDialog method.

Add the following code in loadSurpriseDialog():

CustomFruitDialog().apply {
 listener = object : CustomFruitDialog.Listener {
   override fun onDialogButtonClicked() {
     dismiss()
   }
 }
}.show(supportFragmentManager, TAG_FRUIT_DIALOG)

This creates an instance of your newly defined CustomFruitDialog, implements the click listener to dismiss the dialog and shows the dialog.

Build and run. Tap the Mystery fruit card to see the custom dialog.

The Custom Dialog shows the mystery fruit: Pears.

Progress Indicators

It’s common for there to be periods of short delay while a user interacts with your app. Perhaps the app needs to make a request to a server and wait for it to return the information needed to populate the fields on the screen. Or maybe it needs to retrieve data from a database and do some quick processing to filter out irrelevant entries. These tasks might take only seconds, but if there is nothing to indicate the app is working behind the scenes, the user could be left wondering if the app is even working at all.

This is where Progress Indicators come in. They fill the time with a moving indicator on the screen, reassuring the user that the app has registered the request and is working on it.

An hourglass.

Adding a Delay

Imagine your app must call a server before it can show the mystery fruit. It could take a couple of seconds to get the result. Use Handler to force a delay before showing the dialog, emulating the real-world example.

In the project, navigate to loadSurpriseDialog() in MainActivity.kt. Wrap your existing CustomFruitDialog implementation with the following code:

Handler(mainLooper).postDelayed(Runnable {
  // Your existing CustomFruitDialog() implementation
}, DELAY)

This code uses Handler to execute the Runnable block after the defined delay. DELAY is a constant defined in the companion object at the bottom of the file. It is currently set to 2,000 milliseconds (two seconds).

Build and run. Tap the Mystery fruit card. You’ll notice that without any indication of progress, even two seconds can feel like quite a long time — long enough to start wondering if maybe you should tap the button to try again.

Animation with a delay before showing the Mystery fruit dialog.

Adding the Progress Indicator

To add a progress indicator, you need to declare it in the XML file. Open activity_main.xml and replace TODO: add progress bar with this:

<ProgressBar
 android:id="@+id/progress"
 style="@android:style/Widget.Material.Light.ProgressBar.Large.Inverse"
 android:layout_width="wrap_content"
 android:layout_height="wrap_content"
 android:visibility="gone"
 app:layout_constraintBottom_toTopOf="@id/button_add"
 app:layout_constraintEnd_toEndOf="parent"
 app:layout_constraintStart_toStartOf="parent"
 app:layout_constraintTop_toBottomOf="@id/card_mystery" />

This adds a Progress Bar, which displays as a circle by default. This view has android:visibility="gone", so it’s initially hidden and you can choose when to show it from the code.

Note: The Android implementation of the Material Component Progress Indicator is still planned. Keep an eye on the official release notes to stay updated.

Although the Material Component is not available in a stable channel yet, you can still use Material styles to keep the theme consistent. If you wanted to show a horizontal bar instead, you could set a style such as “@android:style/Widget.Material.Light.ProgressBar.Horizontal”.

Find loadSurpriseDialog() in MainActivity.kt. Add the following line to the beginning of the function:

progress.visibility = View.VISIBLE

This makes the progress bar visible on the screen. The progress bar is indeterminate by default, so once it’s visible it will keep spinning until it’s hidden. Add the following to the top of the Runnable block:

progress.visibility = View.GONE

This hides the progress bar again once the delay elapses. Your code looks like this:

The loadSurpriseDialog method with code for hiding and showing the progress bar.

Build and run. Tap the Mystery fruit card. Now, you’ll see the indicator and should feel assured the app has registered the button tap and is working to complete your request.

Animation showing the progress bar before opening the dialog with mystery fruit.

Snackbars

A Snackbar is a useful little panel that pops up at the bottom of the screen to display a short piece of feedback to the user. It can either persist until dismissed by the user or show for a set amount of time. Snackbars can also display an optional button to trigger an action when tapped.

Earlier in the tutorial, you added a Simple Dialog to let the user add a fruit item. You might remember that you replaced setItems() when you added the Confirmation Dialog. Now, you’ll revert this to show the Simple Dialog again.

Replace the following code in showAddFruitDialog():

.setNeutralButton(resources.getString(R.string.dialog_cancel)) { dialog, _ ->
      dialog.cancel()
    }
.setPositiveButton(resources.getString(R.string.dialog_add_fruit_positive_button))
    { dialog, _ ->
      checkedItems.forEachIndexed { fruitItem, isChecked ->
        if (isChecked) updateFruitQuantity(fruitItem, true)
      }
      dialog.dismiss()
    }
.setMultiChoiceItems(fruitItems, checkedItems) { _, position, checked ->
      checkedItems[position] = checked
    }

with these lines:

.setItems(fruitItems) { dialog, selectedFruitItem ->
     updateFruitQuantity(selectedFruitItem, true)
     dialog.dismiss()
     showSnackbar(selectedFruitItem)
   }

As you can remember, the item click listener is already set up to call showSnackbar(). Your code now looks like this:

The code prepared implementing for the Snackbar.

Adding a Snackbar

When the user selects fruit from the dialog, the dialog closes and the quantity gets updated. It’s possible the user might not notice the quantity value changing or might accidentally tap the wrong type of fruit. In both of these cases, a Snackbar can provide information and assurance to the user by displaying feedback in the form of a confirmation message and providing an “undo” action.

Now, open MainActivity.kt and find showSnackbar().

The code with the empty showSnackbar method.

Replace TODO in showSnackbar() with the following:

val snackbarText = getString(R.string.snackbar_fruit_added, fruitItems[selectedFruitItem])
Snackbar.make(layout_main, snackbarText, Snackbar.LENGTH_LONG)
   .setAction(R.string.snackbar_undo) {
     updateFruitQuantity(selectedFruitItem, false)
   }
   .show()

First, this code creates the string value snackbarText. Here, it uses the position of the selected fruit item from the dialog to get the name from the list of fruits. This name is then inserted into the string resource value for the Snackbar to display.

Snackbar.make() creates a Snackbar with a parent view from which the Snackbar can find an ancestor ViewGroup in which to display itself, the text to be displayed and how long to display it. There are three default options: LENGTH_LONG, LENGTH_SHORT and LENGTH_INDEFINITE.

Note: If you want to include other Material Components in your layout, such as a FloatingActionButton, it’s recommended to use a CoordinatorLayout as the container for your screen layout. This allows the system to handle moving the other components up the screen when displaying the Snackbar and provides extra swipe-to-dismiss functionality.

In this case, you want to show an undo action. setAction() takes the string for the button, and a click listener which resets the previously updated fruit quantity value.

Finally, show() displays the Snackbar on the screen.

Build and run. Tap Add fruit and select a fruit item from the dialog. The Snackbar appears at the bottom of the screen. If you tap undo, you’ll see the quantity numbers reduce accordingly.

Animation showing the Snackbar.

Toasts

Toasts have been available for developers to use in their Android apps for a long time. The simplest form of pop-up message, they are unobtrusive and short-lived, wrap around their content and display at the bottom of the screen.

You may choose a Snackbar over a Toast if you need an action alongside the message or to ensure the app displays the message in the same UI where the action occurred.

The sample project contains a Copy list button, which saves the list of fruits with their quantities to the clipboard on the device. You can show a simple Toast to confirm to the user that the app successfully saved the content.

In the project, open MainActivity.kt. The button with ID button_copy already has a click listener, which calls saveToClipboard(). This method handles saving the text to the clipboard, then calls showToast(). Here, you’ll add code to display the Toast.

The code of the empty showToast method.

Add the following code:

Toast.makeText(this, R.string.toast_copied_to_clipboard, Toast.LENGTH_LONG).show()

This creates a Toast with the provided string to display and how long to show it.

Build and run. Tap Copy list, and you’ll see the Toast appear at the bottom of the screen.

Animation showing the Toast.

Where to Go From Here?

Download the final project by clicking Download Materials at the top or bottom of this tutorial.

You’ve now learned some of the different ways you can provide useful alerts and hints to improve users’ experience of your app.

If you want to keep experimenting, there are many areas for additional customization. For example, you could try changing the theme of your Snackbar to change the look and feel of your app. The Material Components documentation has a lot of advice on theming. Or you could try adding your own custom touch responses and loading indicators using animations. Start with the Google guide for some inspiration.

There is more to learn about dialogs too. Why not try adding the MaterialTimePicker or MaterialDatePicker to your app to see how they work. You could also try creating another custom dialog with a more complex layout.

Android robot dances.

Hopefully, you’ve learned a lot and had some fun on the way. If you have any comments or questions, please join the forum discussion below.

Average Rating

5/5

Add a rating for this content

3 ratings

More like this

Contributors

Comments