Add a Search Icon to Your Jetpack Compose App Bar

Adding a search icon to your app bar is a common requirement in modern Android apps. Jetpack Compose, Google’s modern UI toolkit, simplifies this process with its declarative approach. In this blog post, we’ll explore how to seamlessly integrate a search icon into your app bar, handle user interactions, and implement best practices for a clean and maintainable codebase.

Why Use Jetpack Compose for App Bars?

Jetpack Compose offers a flexible and concise way to create app bars compared to the traditional XML-based approach. Its composable functions allow you to define UI components programmatically, making it easier to:

  • Maintain a consistent UI across your app.

  • Implement dynamic behavior for your app bar components.

  • Reduce boilerplate code and improve readability.

With Jetpack Compose, adding a search icon and managing its functionality becomes intuitive and efficient.

Prerequisites

Before we begin, ensure that:

  1. You have Android Studio Flamingo or later installed.

  2. Your project uses Jetpack Compose with a minimum version of 1.2.0.

  3. You’re familiar with the basics of composable functions and state management in Jetpack Compose.

Setting Up the App Bar

Basic Scaffold Setup

Jetpack Compose provides the Scaffold composable for layouts with top app bars, bottom navigation, and floating action buttons. Here’s how to set up a basic scaffold:

@Composable
fun MainScreen() {
    Scaffold(
        topBar = { AppBar() }
    ) {
        // Main content here
    }
}

Creating the App Bar

Use the TopAppBar composable to create the app bar:

@Composable
fun AppBar() {
    TopAppBar(
        title = { Text("My App") },
        actions = {
            // Add actions here
        }
    )
}

At this stage, the app bar displays a title but no actions.

Adding the Search Icon

Step 1: Adding the Icon

To add a search icon, use the IconButton composable inside the actions parameter of TopAppBar:

@Composable
fun AppBar() {
    TopAppBar(
        title = { Text("My App") },
        actions = {
            IconButton(onClick = { /* Handle click */ }) {
                Icon(
                    imageVector = Icons.Default.Search,
                    contentDescription = "Search"
                )
            }
        }
    )
}

This adds a clickable search icon to the app bar. When the icon is clicked, the onClick lambda is triggered.

Step 2: Managing Search State

To make the search functionality dynamic, manage the search state using MutableState:

@Composable
fun AppBar() {
    var isSearchActive by remember { mutableStateOf(false) }

    TopAppBar(
        title = {
            if (isSearchActive) {
                TextField(
                    value = "",
                    onValueChange = { /* Handle text input */ },
                    placeholder = { Text("Search...") }
                )
            } else {
                Text("My App")
            }
        },
        actions = {
            IconButton(onClick = { isSearchActive = !isSearchActive }) {
                Icon(
                    imageVector = Icons.Default.Search,
                    contentDescription = "Search"
                )
            }
        }
    )
}

Here, the isSearchActive state determines whether the search bar or the default title is displayed.

Improving User Experience

Step 1: Adding a Back Icon

When the search bar is active, users may want an easy way to return to the default app bar. Add a back icon that appears during search mode:

TopAppBar(
    navigationIcon = {
        if (isSearchActive) {
            IconButton(onClick = { isSearchActive = false }) {
                Icon(
                    imageVector = Icons.Default.ArrowBack,
                    contentDescription = "Back"
                )
            }
        }
    },
    title = { /* Same as before */ },
    actions = { /* Same as before */ }
)

Step 2: Handling Search Input

Capture and process user input in the search bar:

TextField(
    value = searchText,
    onValueChange = { searchText = it },
    placeholder = { Text("Search...") },
    singleLine = true
)

Use the onValueChange lambda to update the search query and filter results dynamically.

Step 3: Clearing the Search Bar

Provide an option to clear the search input for better usability:

TextField(
    value = searchText,
    onValueChange = { searchText = it },
    placeholder = { Text("Search...") },
    singleLine = true,
    trailingIcon = {
        if (searchText.isNotEmpty()) {
            IconButton(onClick = { searchText = "" }) {
                Icon(
                    imageVector = Icons.Default.Close,
                    contentDescription = "Clear"
                )
            }
        }
    }
)

Advanced Use Cases

Integrating Search with ViewModel

For better separation of concerns, manage the search state in a ViewModel:

class SearchViewModel : ViewModel() {
    var searchText by mutableStateOf("")
        private set

    fun updateSearchText(newText: String) {
        searchText = newText
    }
}

In your composable:

@Composable
fun AppBar(viewModel: SearchViewModel = viewModel()) {
    val searchText by viewModel.searchText

    TextField(
        value = searchText,
        onValueChange = { viewModel.updateSearchText(it) },
        placeholder = { Text("Search...") }
    )
}

This approach improves testability and adheres to MVVM architecture principles.

Adding Search Suggestions

Enhance user experience by showing suggestions as the user types. Use a LazyColumn to display suggestions below the search bar:

LazyColumn {
    items(filteredSuggestions) { suggestion ->
        Text(
            text = suggestion,
            modifier = Modifier.clickable { /* Handle selection */ }
        )
    }
}

Filter suggestions dynamically based on the user’s input.

Best Practices

  1. Use Material Design Guidelines: Follow Material Design principles for consistent UI/UX.

  2. Optimize Performance: Avoid unnecessary recompositions by using remember and derivedStateOf.

  3. Test Thoroughly: Ensure the search functionality works across different screen sizes and configurations.

  4. Handle Edge Cases: Validate input and handle empty or invalid search queries gracefully.

Conclusion

Adding a search icon to your Jetpack Compose app bar is straightforward yet highly customizable. By leveraging state management, MVVM architecture, and Material Design guidelines, you can create a polished and user-friendly search experience. With Jetpack Compose, the possibilities for enhancing your app’s functionality and design are endless.

Implement these techniques in your next project to see the power of Jetpack Compose in action. Happy coding!