Material 3
EN

Discovering Material 3 for Android

This series of articles is designed to help you explore the latest addition to Material 3 for Android. In this series, we will take a closer look at the different components and features of Material 3, and explore how they can be used to create modern and user-friendly Android apps.

4 min read
Contents

Search for data within an application (and inside a list of items) is a powerful feature that makes it easy for users to find what they are looking for. In this article, we will discuss the newly added experimental Material 3 SearchBar component, its benefits, and how to use it.

Two Search Configurations

We have two types of configuration available to us:

  • SearchBar, acting as a “full screen” search view
  • DockedSearchBar, acting as a “floating” search view

Both configurations allow us to perform queries and show results in a “dropdown” style.

⚠️ As mentioned in previous articles about DatePicker, it is important to note that the SearchBar and DockedSearchBar components in Jetpack Compose are still experimental and were recently released. This means that it may not be suitable for production use and could contain bugs or other issues.

Material 3 DockedSearchBar

A DockedSearchBar displays search results in a bounded table below the input field. It is meant to be an alternative to SearchBar when expanding to full-screen size.

API for the DockedSearchBar looks like this:

@ExperimentalMaterial3Api
@Composable
fun DockedSearchBar(
    query: String,
    onQueryChange: (String) -> Unit,
    onSearch: (String) -> Unit,
    active: Boolean,
    onActiveChange: (Boolean) -> Unit,
    modifier: Modifier = Modifier,
    enabled: Boolean = true,
    placeholder: @Composable (() -> Unit)? = null,
    leadingIcon: @Composable (() -> Unit)? = null,
    trailingIcon: @Composable (() -> Unit)? = null,
    shape: Shape = SearchBarDefaults.dockedShape,
    colors: SearchBarColors = SearchBarDefaults.colors(),
    tonalElevation: Dp = SearchBarDefaults.Elevation,
    interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
    content: @Composable ColumnScope.() -> Unit,
)

To implement a DockedSearchBar, we will need to remember two values in the composition process:

  • text: String the value of the TextField in the search box
  • active: Boolean are we showing or not results in the SearchBar?
var text by rememberSaveable { mutableStateOf("") }
var active by rememberSaveable { mutableStateOf(false) }

Jetpack Compose provides a nice way to use a “floating box” using zIndex() a nice workaround to ensure focus in the search TextField:

Box(Modifier.fillMaxSize()) {
  // Talkback focus order sorts based on x and y position before considering z-index
  Box(Modifier
      .semantics { isContainer = true }
      .zIndex(1f)
      .fillMaxWidth()) {
    DockedSearchBar(
        modifier = Modifier
            .align(Alignment.TopCenter)
            .padding(top = 8.dp),
        query = text,
        onQueryChange = { text = it },
        onSearch = { active = false },
        active = active,
        onActiveChange = { active = it },
        placeholder = { Text("Hinted search text") },
        leadingIcon = { Icon(Icons.Rounded.Search, contentDescription = null) },
        trailingIcon = { Icon(Icons.Rounded.MoreVert, contentDescription = null) },
    ) {
      LazyColumn(
          modifier = Modifier.fillMaxWidth(),
          contentPadding = PaddingValues(16.dp),
          verticalArrangement = Arrangement.spacedBy(4.dp)
      ) {
        items(4) { idx ->
          val resultText = "Suggestion $idx"
          ListItem(
              modifier = Modifier.clickable {
                text = resultText
                active = false
              },
              headlineContent = {
                Text(
                    text = resultText,
                    style = MaterialTheme.typography.titleMedium
                )
              },
              supportingContent = {
                Text(
                    text = "Additional info",
                    style = MaterialTheme.typography.titleMedium
                )
              },
              leadingContent = { Icon(Icons.Rounded.Star, contentDescription = null) },
          )
        }
      }
    }
  }
}

A SearchBar displays search results in a bounded table below the input field. It is meant to be a full-screen search view.

API for SearchBar is similar to DockedSearchBar, but with an additional windowInsets attribute:

@ExperimentalMaterial3Api
@Composable
fun SearchBar(
    query: String,
    onQueryChange: (String) -> Unit,
    onSearch: (String) -> Unit,
    active: Boolean,
    onActiveChange: (Boolean) -> Unit,
    modifier: Modifier = Modifier,
    enabled: Boolean = true,
    placeholder: @Composable (() -> Unit)? = null,
    leadingIcon: @Composable (() -> Unit)? = null,
    trailingIcon: @Composable (() -> Unit)? = null,
    shape: Shape = SearchBarDefaults.inputFieldShape,
    colors: SearchBarColors = SearchBarDefaults.colors(),
    tonalElevation: Dp = SearchBarDefaults.Elevation,
    windowInsets: WindowInsets = SearchBarDefaults.windowInsets,
    interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
    content: @Composable ColumnScope.() -> Unit,
)

To use SearchBar take the sample above and just change DockedSearchBar to SearchBar and enjoy!

Layouts should adapt to fit different screen sizes. On mobile devices, the search view should be a full-screen modal, while on a larger screen, the search view should be a modal connected to the search bar and scale with the search bar width.

The rule of thumb is: use a SearchBar on mobile devices, and switch to a modal search view (DockedSearchBar) on larger screens.

Benefits

One of the benefits of the SearchBar component in Jetpack Compose is that it automatically manages nice animations. When the user taps on the search field, the leading icon smoothly transitions to the left and the input field expands to fill the space. When the user clears the search field, the trailing icon smoothly transitions to the right and the input field contracts to its original size.