From d751d51e9e36fa52badefc0975968575546b9b49 Mon Sep 17 00:00:00 2001 From: Jake Roseman Date: Tue, 25 Feb 2025 13:13:13 +0000 Subject: [PATCH 1/3] Refactor search bar examples --- .../compose/snippets/components/SearchBar.kt | 193 +++++++++++------- 1 file changed, 119 insertions(+), 74 deletions(-) diff --git a/compose/snippets/src/main/java/com/example/compose/snippets/components/SearchBar.kt b/compose/snippets/src/main/java/com/example/compose/snippets/components/SearchBar.kt index 78c37b8e5..a81fe96b6 100644 --- a/compose/snippets/src/main/java/com/example/compose/snippets/components/SearchBar.kt +++ b/compose/snippets/src/main/java/com/example/compose/snippets/components/SearchBar.kt @@ -68,26 +68,33 @@ fun SearchBarExamples() { var currentExample by remember { mutableStateOf(null) } when (currentExample) { - "basic" -> SearchBarBasicFilterList() - "advanced" -> AppSearchBar() + "simple" -> SimpleSearchBarExample() + "fancy" -> CustomizableSearchBarExample() else -> { - Button(onClick = { currentExample = "basic" }) { - Text("Basic search bar with filter") + Button(onClick = { currentExample = "simple" }) { + Text("Simple SearchBar") } - Button(onClick = { currentExample = "advanced" }) { - Text("Advanced search bar with filter") + Button(onClick = { currentExample = "fancy" }) { + Text("Customizable SearchBar") } } } } } +// [START android_compose_components_simple_searchbar] @OptIn(ExperimentalMaterial3Api::class) -// [START android_compose_components_searchbarbasicfilterlist] @Composable -fun SearchBarBasicFilterList(modifier: Modifier = Modifier) { - var text by rememberSaveable { mutableStateOf("") } +fun SimpleSearchBar( + query: String, + onQueryChange: (String) -> Unit, + onSearch: (String) -> Unit, + searchResults: List, + onResultClick: (String) -> Unit, + modifier: Modifier = Modifier +) { var expanded by rememberSaveable { mutableStateOf(false) } + Box( modifier .fillMaxSize() @@ -99,26 +106,27 @@ fun SearchBarBasicFilterList(modifier: Modifier = Modifier) { .semantics { traversalIndex = 0f }, inputField = { SearchBarDefaults.InputField( - query = text, - onQueryChange = { text = it }, - onSearch = { expanded = false }, + query = query, + onQueryChange = onQueryChange, + onSearch = { + onSearch(query) + expanded = false + }, expanded = expanded, onExpandedChange = { expanded = it }, - placeholder = { Text("Hinted search text") } + placeholder = { Text("Search") } ) }, expanded = expanded, onExpandedChange = { expanded = it }, ) { Column(Modifier.verticalScroll(rememberScrollState())) { - repeat(4) { index -> - val resultText = "Suggestion $index" + searchResults.forEach { result -> ListItem( - headlineContent = { Text(resultText) }, - supportingContent = { Text("Additional info") }, + headlineContent = { Text(result) }, modifier = Modifier .clickable { - text = resultText + onResultClick(result) expanded = false } .fillMaxWidth() @@ -128,27 +136,52 @@ fun SearchBarBasicFilterList(modifier: Modifier = Modifier) { } } } -// [END android_compose_components_searchbarbasicfilterlist] +// [END android_compose_components_simple_searchbar] @Preview(showBackground = true) @Composable -private fun SearchBarBasicFilterListPreview() { - SearchBarBasicFilterList() +private fun SimpleSearchBarExample() { + var query by rememberSaveable { mutableStateOf("") } + val items = listOf( + "Cupcake", "Donut", "Eclair", "Froyo", "Gingerbread", "Honeycomb", + "Ice Cream Sandwich", "Jelly Bean", "KitKat", "Lollipop" + ) + + val filteredItems by remember { + derivedStateOf { + if (query.isEmpty()) { + emptyList() + } else { + items.filter { it.contains(query, ignoreCase = true) } + } + } + } + + SimpleSearchBar( + query = query, + onQueryChange = { query = it }, + onSearch = { /* Handle search submission */ }, + searchResults = filteredItems, + onResultClick = { query = it } + ) } -// [START android_compose_components_searchbarfilterlist] +// [START android_compose_components_customizable_searchbar] @OptIn(ExperimentalMaterial3Api::class) @Composable -fun SearchBarFilterList( - list: List, +fun CustomizableSearchBar( + query: String, + onQueryChange: (String) -> Unit, + onSearch: (String) -> Unit, + searchResults: List, + onResultClick: (String) -> Unit, + placeholder: @Composable () -> Unit = { Text("Search") }, + leadingIcon: @Composable (() -> Unit)? = { Icon(Icons.Default.Search, contentDescription = "Search") }, + trailingIcon: @Composable (() -> Unit)? = null, + supportingContent: (@Composable (String) -> Unit)? = null, + leadingContent: (@Composable () -> Unit)? = null, modifier: Modifier = Modifier ) { - var text by rememberSaveable { mutableStateOf("") } - val filteredList by remember { - derivedStateOf { - list.filter { it.lowercase().contains(text.lowercase()) } - } - } var expanded by rememberSaveable { mutableStateOf(false) } Box( @@ -162,35 +195,33 @@ fun SearchBarFilterList( .semantics { traversalIndex = 0f }, inputField = { SearchBarDefaults.InputField( - query = text, - onQueryChange = { text = it }, - onSearch = { expanded = false }, + query = query, + onQueryChange = onQueryChange, + onSearch = { + onSearch(query) + expanded = false + }, expanded = expanded, onExpandedChange = { expanded = it }, - placeholder = { Text("Hinted search text") }, - leadingIcon = { Icon(Icons.Default.Search, contentDescription = "Search") }, - trailingIcon = { Icon(Icons.Default.MoreVert, contentDescription = "More options") }, + placeholder = placeholder, + leadingIcon = leadingIcon, + trailingIcon = trailingIcon ) }, expanded = expanded, onExpandedChange = { expanded = it }, ) { LazyColumn { - items(count = filteredList.size) { index -> - val resultText = filteredList[index] + items(count = searchResults.size) { index -> + val resultText = searchResults[index] ListItem( headlineContent = { Text(resultText) }, - supportingContent = { Text("Additional info") }, - leadingContent = { - Icon( - Icons.Filled.Star, - contentDescription = "Starred item" - ) - }, + supportingContent = supportingContent?.let { { it(resultText) } }, + leadingContent = leadingContent, colors = ListItemDefaults.colors(containerColor = Color.Transparent), modifier = Modifier .clickable { - text = resultText + onResultClick(resultText) expanded = false } .fillMaxWidth() @@ -199,6 +230,45 @@ fun SearchBarFilterList( } } } + } +} +// [END android_compose_components_customizable_searchbar] + +@Preview(showBackground = true) +@Composable +fun CustomizableSearchBarExample() { + var query by rememberSaveable { mutableStateOf("") } + val items = listOf( + "Cupcake", "Donut", "Eclair", "Froyo", "Gingerbread", "Honeycomb", + "Ice Cream Sandwich", "Jelly Bean", "KitKat", "Lollipop", "Marshmallow", + "Nougat", "Oreo", "Pie" + ) + + val filteredItems by remember { + derivedStateOf { + if (query.isEmpty()) { + items + } else { + items.filter { it.contains(query, ignoreCase = true) } + } + } + } + + Column(modifier = Modifier.fillMaxSize()) { + CustomizableSearchBar( + query = query, + onQueryChange = { query = it }, + onSearch = { /* Handle search submission */ }, + searchResults = filteredItems, + onResultClick = { query = it }, + placeholder = { Text("Search desserts") }, + leadingIcon = { Icon(Icons.Default.Search, contentDescription = "Search") }, + trailingIcon = { Icon(Icons.Default.MoreVert, contentDescription = "More options") }, + supportingContent = { Text("Android dessert") }, + leadingContent = { Icon(Icons.Filled.Star, contentDescription = "Starred item") } + ) + + // Display the filtered list below the search bar LazyColumn( contentPadding = PaddingValues( start = 16.dp, @@ -211,34 +281,9 @@ fun SearchBarFilterList( traversalIndex = 1f }, ) { - items(count = filteredList.size) { - Text(text = filteredList[it]) + items(count = filteredItems.size) { + Text(text = filteredItems[it]) } } } -} -// [END android_compose_components_searchbarfilterlist] - -@Preview(showBackground = true) -@Composable -fun AppSearchBar(modifier: Modifier = Modifier) { - SearchBarFilterList( - list = listOf( - "Cupcake", - "Donut", - "Eclair", - "Froyo", - "Gingerbread", - "Honeycomb", - "Ice Cream Sandwich", - "Jelly Bean", - "KitKat", - "Lollipop", - "Marshmallow", - "Nougat", - "Oreo", - "Pie" - ), - modifier - ) -} +} \ No newline at end of file From 705a6cc36900429f3573bced1b1280ff44a4b8a5 Mon Sep 17 00:00:00 2001 From: Jake Roseman Date: Tue, 25 Feb 2025 13:27:20 +0000 Subject: [PATCH 2/3] Add comments --- .../compose/snippets/components/SearchBar.kt | 40 ++++++++++++------- 1 file changed, 25 insertions(+), 15 deletions(-) diff --git a/compose/snippets/src/main/java/com/example/compose/snippets/components/SearchBar.kt b/compose/snippets/src/main/java/com/example/compose/snippets/components/SearchBar.kt index a81fe96b6..cf92482f7 100644 --- a/compose/snippets/src/main/java/com/example/compose/snippets/components/SearchBar.kt +++ b/compose/snippets/src/main/java/com/example/compose/snippets/components/SearchBar.kt @@ -26,6 +26,8 @@ import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.text.input.TextFieldState +import androidx.compose.foundation.text.input.rememberTextFieldState import androidx.compose.foundation.verticalScroll import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.MoreVert @@ -86,13 +88,12 @@ fun SearchBarExamples() { @OptIn(ExperimentalMaterial3Api::class) @Composable fun SimpleSearchBar( - query: String, - onQueryChange: (String) -> Unit, + textFieldState: TextFieldState, onSearch: (String) -> Unit, searchResults: List, - onResultClick: (String) -> Unit, modifier: Modifier = Modifier ) { + // Controls expansion state of the search bar var expanded by rememberSaveable { mutableStateOf(false) } Box( @@ -106,10 +107,10 @@ fun SimpleSearchBar( .semantics { traversalIndex = 0f }, inputField = { SearchBarDefaults.InputField( - query = query, - onQueryChange = onQueryChange, + query = textFieldState.text.toString(), + onQueryChange = { textFieldState.edit { replace(0, length, it) } }, onSearch = { - onSearch(query) + onSearch(textFieldState.text.toString()) expanded = false }, expanded = expanded, @@ -120,13 +121,14 @@ fun SimpleSearchBar( expanded = expanded, onExpandedChange = { expanded = it }, ) { + // Display search results in a scrollable column Column(Modifier.verticalScroll(rememberScrollState())) { searchResults.forEach { result -> ListItem( headlineContent = { Text(result) }, modifier = Modifier .clickable { - onResultClick(result) + textFieldState.edit { replace(0, length, result) } expanded = false } .fillMaxWidth() @@ -141,28 +143,29 @@ fun SimpleSearchBar( @Preview(showBackground = true) @Composable private fun SimpleSearchBarExample() { - var query by rememberSaveable { mutableStateOf("") } + // Create and remember the text field state + val textFieldState = rememberTextFieldState() val items = listOf( "Cupcake", "Donut", "Eclair", "Froyo", "Gingerbread", "Honeycomb", "Ice Cream Sandwich", "Jelly Bean", "KitKat", "Lollipop" ) + // Filter items based on the current search text val filteredItems by remember { derivedStateOf { - if (query.isEmpty()) { + val searchText = textFieldState.text.toString() + if (searchText.isEmpty()) { emptyList() } else { - items.filter { it.contains(query, ignoreCase = true) } + items.filter { it.contains(searchText, ignoreCase = true) } } } } SimpleSearchBar( - query = query, - onQueryChange = { query = it }, + textFieldState = textFieldState, onSearch = { /* Handle search submission */ }, - searchResults = filteredItems, - onResultClick = { query = it } + searchResults = filteredItems ) } @@ -175,6 +178,7 @@ fun CustomizableSearchBar( onSearch: (String) -> Unit, searchResults: List, onResultClick: (String) -> Unit, + // Customization options placeholder: @Composable () -> Unit = { Text("Search") }, leadingIcon: @Composable (() -> Unit)? = { Icon(Icons.Default.Search, contentDescription = "Search") }, trailingIcon: @Composable (() -> Unit)? = null, @@ -182,6 +186,7 @@ fun CustomizableSearchBar( leadingContent: (@Composable () -> Unit)? = null, modifier: Modifier = Modifier ) { + // Track expanded state of search bar var expanded by rememberSaveable { mutableStateOf(false) } Box( @@ -194,6 +199,7 @@ fun CustomizableSearchBar( .align(Alignment.TopCenter) .semantics { traversalIndex = 0f }, inputField = { + // Customizable input field implementation SearchBarDefaults.InputField( query = query, onQueryChange = onQueryChange, @@ -211,6 +217,7 @@ fun CustomizableSearchBar( expanded = expanded, onExpandedChange = { expanded = it }, ) { + // Show search results in a lazy column for better performance LazyColumn { items(count = searchResults.size) { index -> val resultText = searchResults[index] @@ -237,6 +244,7 @@ fun CustomizableSearchBar( @Preview(showBackground = true) @Composable fun CustomizableSearchBarExample() { + // Manage query state var query by rememberSaveable { mutableStateOf("") } val items = listOf( "Cupcake", "Donut", "Eclair", "Froyo", "Gingerbread", "Honeycomb", @@ -244,6 +252,7 @@ fun CustomizableSearchBarExample() { "Nougat", "Oreo", "Pie" ) + // Filter items based on query val filteredItems by remember { derivedStateOf { if (query.isEmpty()) { @@ -261,6 +270,7 @@ fun CustomizableSearchBarExample() { onSearch = { /* Handle search submission */ }, searchResults = filteredItems, onResultClick = { query = it }, + // Customize appearance with optional parameters placeholder = { Text("Search desserts") }, leadingIcon = { Icon(Icons.Default.Search, contentDescription = "Search") }, trailingIcon = { Icon(Icons.Default.MoreVert, contentDescription = "More options") }, @@ -272,7 +282,7 @@ fun CustomizableSearchBarExample() { LazyColumn( contentPadding = PaddingValues( start = 16.dp, - top = 72.dp, + top = 72.dp, // Provides space for the search bar end = 16.dp, bottom = 16.dp ), From 79fcb8535f46d241daa8ded2a2cccbc2c16e0ba2 Mon Sep 17 00:00:00 2001 From: jakeroseman <122034773+jakeroseman@users.noreply.github.com> Date: Tue, 25 Feb 2025 13:35:59 +0000 Subject: [PATCH 3/3] Apply Spotless --- .../java/com/example/compose/snippets/components/SearchBar.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/compose/snippets/src/main/java/com/example/compose/snippets/components/SearchBar.kt b/compose/snippets/src/main/java/com/example/compose/snippets/components/SearchBar.kt index cf92482f7..f3243e299 100644 --- a/compose/snippets/src/main/java/com/example/compose/snippets/components/SearchBar.kt +++ b/compose/snippets/src/main/java/com/example/compose/snippets/components/SearchBar.kt @@ -282,7 +282,7 @@ fun CustomizableSearchBarExample() { LazyColumn( contentPadding = PaddingValues( start = 16.dp, - top = 72.dp, // Provides space for the search bar + top = 72.dp, // Provides space for the search bar end = 16.dp, bottom = 16.dp ), @@ -296,4 +296,4 @@ fun CustomizableSearchBarExample() { } } } -} \ No newline at end of file +}