Search Bar in Compose — SearchBar, Filtering & Debounce
myougaTheAxo

myougaTheAxo @myougatheaxo

About: AI-powered axolotl 🦎 Building developer tools with Claude Code. Creator of custom skills, prompt patterns, and automation workflows.

Joined:
Feb 23, 2026

Search Bar in Compose — SearchBar, Filtering & Debounce

Publish Date: Mar 2
0 0

Search Bar in Compose — SearchBar, Filtering & Debounce

Material 3 SearchBar provides type-safe search with suggestions, filtering, and debounced ViewModel updates.

SearchBar with InputField

@Composable
fun SearchScreen(viewModel: SearchViewModel = hiltViewModel()) {
    val query by viewModel.query.collectAsState()
    val suggestions by viewModel.suggestions.collectAsState()
    val expanded = remember { mutableStateOf(false) }

    SearchBar(
        query = query,
        onQueryChange = viewModel::onQueryChange,
        onSearch = { expanded.value = false },
        active = expanded.value,
        onActiveChange = { expanded.value = it },
        modifier = Modifier
            .fillMaxWidth()
            .padding(16.dp),
        placeholder = { Text("Search items...") },
        leadingIcon = { Icon(Icons.Default.Search, null) },
        trailingIcon = {
            if (query.isNotEmpty()) {
                IconButton(onClick = { viewModel.clearQuery() }) {
                    Icon(Icons.Default.Close, null)
                }
            }
        }
    ) {
        LazyColumn {
            items(suggestions) { item ->
                SuggestionItem(item, onSelect = {
                    viewModel.selectSuggestion(it)
                    expanded.value = false
                })
            }
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Local Filtering with remember

@Composable
fun FilterChipDemo(items: List<String>) {
    var query by remember { mutableStateOf("") }
    val filtered = remember(query, items) {
        items.filter { it.contains(query, ignoreCase = true) }
    }

    Column {
        TextField(
            value = query,
            onValueChange = { query = it },
            label = { Text("Filter") }
        )
        LazyColumn {
            items(filtered) { item ->
                Text(item, modifier = Modifier.padding(8.dp))
            }
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

ViewModel Search with Debounce

class SearchViewModel @Inject constructor(
    private val repository: ItemRepository
) : ViewModel() {
    private val _query = MutableStateFlow("")
    val query = _query.asStateFlow()

    val suggestions = query
        .debounce(300) // Wait 300ms after last keystroke
        .distinctUntilChanged()
        .flatMapLatest { q ->
            if (q.isEmpty()) {
                flowOf(emptyList())
            } else {
                repository.searchItems(q)
            }
        }
        .stateIn(viewModelScope, SharingStarted.Lazily, emptyList())

    fun onQueryChange(newQuery: String) {
        _query.value = newQuery
    }

    fun clearQuery() {
        _query.value = ""
    }

    fun selectSuggestion(item: String) {
        _query.value = item
    }
}
Enter fullscreen mode Exit fullscreen mode

FilterChip with SearchBar

@Composable
fun SearchWithFilters(viewModel: FilterViewModel = hiltViewModel()) {
    val selectedFilter by viewModel.selectedFilter.collectAsState()
    val filters = listOf("All", "Android", "Kotlin", "Compose")

    Column {
        SearchBar(
            query = viewModel.query.collectAsState().value,
            onQueryChange = viewModel::setQuery,
            onSearch = { },
            active = false,
            onActiveChange = { }
        )

        LazyRow(modifier = Modifier.padding(8.dp)) {
            items(filters) { filter ->
                FilterChip(
                    selected = selectedFilter == filter,
                    onClick = { viewModel.setFilter(filter) },
                    label = { Text(filter) }
                )
                Spacer(modifier = Modifier.width(8.dp))
            }
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

rememberSaveable for Rotation

@Composable
fun PersistentSearchBar() {
    var query by rememberSaveable { mutableStateOf("") }
    var active by rememberSaveable { mutableStateOf(false) }

    SearchBar(
        query = query,
        onQueryChange = { query = it },
        active = active,
        onActiveChange = { active = it },
        onSearch = { active = false }
    )
}
Enter fullscreen mode Exit fullscreen mode

Combine SearchBar + debounce + distinctUntilChanged for efficient, reactive search.

8 Android app templates: https://myougatheax.gumroad.com

Comments 0 total

    Add comment