I want to create SearchView using jetpack compose, but I can't found any example that could helps me. Thanks in Advance.
-
Which type of view you want can you add screenshot for reference – Ankit Mahadik Nov 15 '19 at 05:32
-
@AnkitMahadik I have attached image to the question – Alexander Suvorov Nov 15 '19 at 16:43
4 Answers
This is a complex but full implementation for a SearchView from scratch. And the result will be as in the gif below, you can customize or remove InitialResults
or Suggestions
if you don't want your initial Composable to be displayed when SearchView is not focused and empty
Full implementation is available in github repository.
1- Create search states with
/**
* Enum class with different values to set search state based on text, focus, initial state and
* results from search.
*
* **InitialResults** represents the initial state before search is initiated. This represents
* the whole screen
*
*/
enum class SearchDisplay {
InitialResults, Suggestions, Results, NoResults
}
2- Then create class where you define your search logic
@Stable
class SearchState(
query: TextFieldValue,
focused: Boolean,
searching: Boolean,
suggestions: List<SuggestionModel>,
searchResults: List<TutorialSectionModel>
) {
var query by mutableStateOf(query)
var focused by mutableStateOf(focused)
var searching by mutableStateOf(searching)
var suggestions by mutableStateOf(suggestions)
var searchResults by mutableStateOf(searchResults)
val searchDisplay: SearchDisplay
get() = when {
!focused && query.text.isEmpty() -> SearchDisplay.InitialResults
focused && query.text.isEmpty() -> SearchDisplay.Suggestions
searchResults.isEmpty() -> SearchDisplay.NoResults
else -> SearchDisplay.Results
}
override fun toString(): String {
return " State query: $query, focused: $focused, searching: $searching " +
"suggestions: ${suggestions.size}, " +
"searchResults: ${searchResults.size}, " +
" searchDisplay: $searchDisplay"
}
}
3- remember state to not update in every composition but only when our seach state changes
@Composable
fun rememberSearchState(
query: TextFieldValue = TextFieldValue(""),
focused: Boolean = false,
searching: Boolean = false,
suggestions: List<SuggestionModel> = emptyList(),
searchResults: List<TutorialSectionModel> = emptyList()
): SearchState {
return remember {
SearchState(
query = query,
focused = focused,
searching = searching,
suggestions = suggestions,
searchResults = searchResults
)
}
}
TutorialSectionModel
is the model i used it can be generic type T or specific type you wish to display
4- Create a hint to be displayed when not focused
@Composable
private fun SearchHint(modifier: Modifier = Modifier) {
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier
.fillMaxSize()
.then(modifier)
) {
Text(
color = Color(0xff757575),
text = "Search a Tag or Description",
)
}
}
I didn't use an Icon
but if you wish you can add one
5- Create a SearchTextfield
that can has cancel button, CircularProgressIndicator to display loading and BasicTextField
to input
/**
* This is a stateless TextField for searching with a Hint when query is empty,
* and clear and loading [IconButton]s to clear query or show progress indicator when
* a query is in progress.
*/
@Composable
fun SearchTextField(
query: TextFieldValue,
onQueryChange: (TextFieldValue) -> Unit,
onSearchFocusChange: (Boolean) -> Unit,
onClearQuery: () -> Unit,
searching: Boolean,
focused: Boolean,
modifier: Modifier = Modifier
) {
val focusRequester = remember { FocusRequester() }
Surface(
modifier = modifier
.then(
Modifier
.height(56.dp)
.padding(
top = 8.dp,
bottom = 8.dp,
start = if (!focused) 16.dp else 0.dp,
end = 16.dp
)
),
color = Color(0xffF5F5F5),
shape = RoundedCornerShape(percent = 50),
) {
CompositionLocalProvider(LocalContentAlpha provides ContentAlpha.medium) {
Box(
contentAlignment = Alignment.CenterStart,
modifier = modifier
) {
if (query.text.isEmpty()) {
SearchHint(modifier.padding(start = 24.dp, end = 8.dp))
}
Row(verticalAlignment = Alignment.CenterVertically) {
BasicTextField(
value = query,
onValueChange = onQueryChange,
modifier = Modifier
.fillMaxHeight()
.weight(1f)
.onFocusChanged {
onSearchFocusChange(it.isFocused)
}
.focusRequester(focusRequester)
.padding(top = 9.dp, bottom = 8.dp, start = 24.dp, end = 8.dp),
singleLine = true
)
when {
searching -> {
CircularProgressIndicator(
modifier = Modifier
.padding(horizontal = 6.dp)
.size(36.dp)
)
}
query.text.isNotEmpty() -> {
IconButton(onClick = onClearQuery) {
Icon(imageVector = Icons.Filled.Cancel, contentDescription = null)
}
}
}
}
}
}
}
}
You can remove CircularProgressBar
or add Icon to Row
which contains BasicTextField
6- SearchBar
with SearchTextField
above and back arrow to return back feature with. AnimatedVisibility
makes sure arrow is animated when we focus BasicTextField in SearchTextField
, it can also be used with Icon as magnifying glass.
@ExperimentalAnimationApi
@OptIn(ExperimentalComposeUiApi::class)
@Composable
fun SearchBar(
query: TextFieldValue,
onQueryChange: (TextFieldValue) -> Unit,
onSearchFocusChange: (Boolean) -> Unit,
onClearQuery: () -> Unit,
onBack: ()-> Unit,
searching: Boolean,
focused: Boolean,
modifier: Modifier = Modifier
) {
val focusManager = LocalFocusManager.current
val keyboardController = LocalSoftwareKeyboardController.current
Row(
modifier = modifier.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically
) {
AnimatedVisibility(visible = focused) {
// Back button
IconButton(
modifier = Modifier.padding(start =2.dp),
onClick = {
focusManager.clearFocus()
keyboardController?.hide()
onBack()
}) {
Icon(imageVector = Icons.Default.ArrowBack, contentDescription = null)
}
}
SearchTextField(
query,
onQueryChange,
onSearchFocusChange,
onClearQuery,
searching,
focused,
modifier.weight(1f)
)
}
}
7- To use SearchBar create a rememberSearchState and update state as
Column is used here because rest of the screen is updated based on SearchState
LaunchedEffect or setting mutableState in ViewModel can be used to set query result or searching
field of state to display loading
@Composable
fun HomeScreen(
modifier: Modifier = Modifier,
viewModel: HomeViewModel,
navigateToTutorial: (String) -> Unit,
state: SearchState = rememberSearchState()
) {
Column(
modifier = modifier.fillMaxSize()
) {
SearchBar(
query = state.query,
onQueryChange = { state.query = it },
onSearchFocusChange = { state.focused = it },
onClearQuery = { state.query = TextFieldValue("") },
onBack = { state.query = TextFieldValue("") },
searching = state.searching,
focused = state.focused,
modifier = modifier
)
LaunchedEffect(state.query.text) {
state.searching = true
delay(100)
state.searchResults = viewModel.getTutorials(state.query.text)
state.searching = false
}
when (state.searchDisplay) {
SearchDisplay.InitialResults -> {
}
SearchDisplay.NoResults -> {
}
SearchDisplay.Suggestions -> {
}
SearchDisplay.Results -> {
}
}
}
}
This is the SearchView
you have in that image :
val (value, onValueChange) = remember { mutableStateOf("") }
TextField(
value = value,
onValueChange = onValueChange,
textStyle = TextStyle(fontSize = 17.sp),
leadingIcon = { Icon(Icons.Filled.Search, null, tint = Color.Gray) },
modifier = Modifier
.padding(10.dp)
.background(Color(0xFFE7F1F1), RoundedCornerShape(16.dp)),
placeholder = { Text(text = "Bun") },
colors = TextFieldDefaults.textFieldColors(
focusedIndicatorColor = Color.Transparent,
unfocusedIndicatorColor = Color.Transparent,
backgroundColor = Color.Transparent,
cursorColor = Color.DarkGray
)
)

- 563
- 4
- 9
TextField(
startingIcon = Icon(bitmap = searchIcon),
placeholder = { Text(...) }
)

- 5,477
- 2
- 11
- 42
Just create component, with FlexRow if you want to create UI like those.
FlexRow(crossAxisAlignment = CrossAxisAlignment.Start) {
inflexible {
drawImageResource(R.drawable.image_search)
}
expanded(1.0f) {
SingleLineEditText(
state,
keyboardType = KeyboardType.Text,
imeAction = ImeAction.Search,
editorStyle = EditorStyle(textStyle = TextStyle(fontSize = 16.sp)),
onImeActionPerformed = {
onSearch(state.value.text)
}
)
}
}

- 3,403
- 1
- 8
- 6
-
2
-
2@VinayGaba But Row doesn't have any expanded or flex functionality, what should be the replacement for that? – Ahmed Ali Feb 05 '21 at 12:55
-
1