Search bar in Android Jetpack Compose
This tutorial explains:
- How SearchBar works
- How search suggestions are shown
- State handling (query, expanded)
- Accessibility with semantics
- Best practices
What is a Search Bar?
- Enter a search query
- View suggestions or results
- Select an item to autofill the search text
Key Concepts
| Concept | Purpose |
|---|---|
| SearchBar | Container for search UI |
| InputField | Handles user text input |
| expanded | Controls visibility of suggestions |
| rememberSaveable | Preserves state on rotation |
| Column | Displays search suggestions |
| ListItem | Each search result |
| semantics | Accessibility (TalkBack) |
Required Dependencies
dependencies {
implementation(platform("androidx.compose:compose-bom:2024.09.00"))
implementation("androidx.compose.material3:material3")
}Basic Search Bar Structure
package com.example.composeapp
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.ListItem
import androidx.compose.material3.SearchBar
import androidx.compose.material3.SearchBarDefaults
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.semantics.isTraversalGroup
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.semantics.traversalIndex
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun SearchBarExample() {
var query by rememberSaveable { mutableStateOf("") }
var expanded by rememberSaveable { mutableStateOf(false) }
val searchResults = listOf(
"Android",
"Jetpack Compose",
"Kotlin",
"Material Design",
"Room Database"
).filter {
it.contains(query, ignoreCase = true)
}
Box(
modifier = Modifier
.fillMaxWidth()
.semantics { isTraversalGroup = true }
) {
SearchBar(
inputField = {
SearchBarDefaults.InputField(
query = query,
onQueryChange = { query = it },
onSearch = { expanded = false },
expanded = expanded,
onExpandedChange = { expanded = it },
placeholder = { Text("Search topics") }
)
},
expanded = expanded,
onExpandedChange = { expanded = it },
modifier = Modifier
.fillMaxWidth()
.semantics { traversalIndex = 0f }
) {
SearchSuggestions(
searchResults = searchResults,
onResultClick = { result ->
query = result
expanded = false
}
)
}
}
}
Search Suggestions UI
@Composable
fun SearchSuggestions(
searchResults: List,
onResultClick: (String) -> Unit
) {
Column(
modifier = Modifier
.fillMaxWidth()
.semantics { traversalIndex = 1f }
) {
searchResults.forEach { result ->
ListItem(
headlineContent = { Text(result) },
modifier = Modifier.clickable {
onResultClick(result)
}
)
}
}
} OUTPUT:
How Everything Works
1. Input Field
SearchBarDefaults.InputField(...)2. Query State
var query by rememberSaveable { mutableStateOf("") }- Stores user input
- Survives screen rotation
- Updates UI automatically
3. Expanded State
var expanded by rememberSaveable { mutableStateOf(false) }- When suggestions are visible
- When search bar is open/closed
4. Search Results Logic
val searchResults = list.filter {
it.contains(query, ignoreCase = true)
}- Filters data dynamically
- UI updates instantly on typing
5. Showing Suggestions
searchResults.forEach { result ->
ListItem(...)
}- Iterates through results
- Creates a clickable suggestion list
6. Handling Click on Suggestion
query = result
expanded = false❓ FREQUENTLY ASKED QUESTIONS (FAQ)
FAQ 1: Does SearchBar automatically manage keyboard?
FAQ 2: Can I show recent searches?
FAQ 3: Does SearchBar support dark mode?
FAQ 4: Can SearchBar work inside LazyColumn?
FAQ 5: Should SearchBar be placed inside Scaffold?
FAQ 6: How to debounce search input?
- LaunchedEffect
- snapshotFlow
- Flow debounce()
FAQ 7: Is SearchBar better than custom search UI?
- Consistent UX
- Accessibility support
- Less boilerplate
FAQ 8: Can I disable suggestions?
FAQ 9: Is SearchBar backward compatible?
FAQ 10: Is SearchBar suitable for large datasets?
- ViewModel
- Repository
- Not directly in UI.
❌ Common Mistakes in Jetpack Compose SearchBar
(Material Design 3)
1. Using
rememberInstead of
rememberSaveable
❌ Wrong
var query by remember { mutableStateOf("") }
⚠️ Problem
State resets on screen rotation.
✅ Correct
var query by rememberSaveable { mutableStateOf("") }
2. Not Collapsing SearchBar After Selection
❌ Wrong
query = result
⚠️ Problem
SearchBar stays open → poor UX.
✅ Correct
query = result
expanded = false
3. Performing Heavy Logic Inside Composable
❌ Wrong
val results = hugeList.filter { expensiveOperation(it) }
⚠️ Problem
Causes lag and recomposition issues.
✅ Correct
- Move logic to ViewModel
- Repository layer
4. Forgetting to Handle Empty Query Case
❌ Wrong
searchResults.forEach { ... }
⚠️ Problem
Suggestions appear even when query is empty.
✅ Correct
if (query.isNotEmpty()) {
searchResults.forEach { ... }
}
5. Not Using expanded State Properly
❌ Wrong
SearchBar(expanded = true)
⚠️ Problem
SearchBar always open.
✅ Correct
var expanded by rememberSaveable { mutableStateOf(false) }
6. Ignoring onSearch Keyboard Action
❌ Wrong
onSearch = {}
⚠️ Problem
Keyboard search button does nothing.
✅ Correct
onSearch = {
expanded = false
}
7. Duplicating Recent Searches
❌ Wrong
recentSearches.add(query)
⚠️ Problem
Same item appears multiple times.
✅ Correct
if (!recentSearches.contains(query)) {
recentSearches.add(0, query)
}
8. Storing UI State in ViewModel Incorrectly
❌ Wrong
val expanded = mutableStateOf(false)
⚠️ Problem
UI state tied incorrectly to business logic.
✅ Correct
Keep UI-only state inside Composable.
9. Missing Accessibility Semantics
❌ Wrong
SearchBar(...)
⚠️ Problem
Poor TalkBack experience.
✅ Correct
Modifier.semantics { isTraversalGroup = true
10. Incorrect TalkBack Traversal Order
❌ Wrong
traversalIndex = 1f
⚠️ Problem
Suggestions read before input field.
✅ Correct
- InputField →
-1f - Suggestions →
1f
11. Filtering List on Every Recomposition
❌ Wrong
val results = list.filter { ... }
⚠️ Problem
Unnecessary recomputations.
✅ Correct
val results by remember(query) {
mutableStateOf(...)
}
12. Not Limiting Recent Search Size
❌ Wrong
recentSearches.add(query)
⚠️ Problem
Unbounded list growth.
✅ Correct
if (recentSearches.size >= 5) {
recentSearches.removeLast()
}
13. Hardcoding Strings
❌ Wrong
Text("Search here")
⚠️ Problem
No localization support.
✅ Correct
Text(stringResource(R.string.search_hint))
14. Not Handling Focus Properly
❌ Problem
SearchBar opens but keyboard doesn’t appear.
✅ Fix
Use default SearchBarDefaults.InputField to manage focus automatically.
15. Forgetting to Clear Query State
❌ Problem
Old query remains when reopening search.
✅ Correct
query = ""
expanded = false
16. Using SearchBar for Non-Search UI
❌ Wrong
Using SearchBar as a normal text field.
✅ Correct
Use TextField for forms and SearchBar only for search.
17. Not Handling Dark Mode
❌ Wrong
Custom colors ignoring theme.
✅ Correct
Use MaterialTheme.colorScheme.
18. Not Debouncing API Calls
❌ Wrong
onQueryChange { callApi(it) }
⚠️ Problem
Too many API calls.
✅ Correct
Use Flow + debounce.
19. Showing Empty UI When No Results
❌ Wrong
Nothing appears.
✅ Correct
Text("No results found")
20. Forgetting to Close Keyboard
❌ Problem
Keyboard stays open after selection.
✅ Fix
Collapse SearchBar or manage focus.
📌 Tip: These mistakes are frequently asked in interviews and occur in real production apps. Avoiding them ensures better UX, performance, and accessibility.


