Learn how to build a search experience with InstantSearch Android and Compose UI.
This guide explains, step by step, how to build a voice search experience using the libraries provided by Algolia and Compose UI.This guide walks you through the steps needed to create a full InstantSearch Android experience from scratch.It includes:
To use InstantSearch, you need an Algolia account.
Sign up for a new account or use the following credentials
(which include a pre-loaded dataset of products appropriate for this guide):
Define a structure that represents the in your index.
For simplicity’s sake, this structure only provides the name of the product.
Add the following data class definition to the Product.kt file:
Kotlin
Report incorrect code
Copy
@Serializabledata class Product( val name: String)
You need three components for the basic search experience:
HitsSearcher performs search requests and obtains search results.
SearchBoxConnector handles a textual input and triggers a search request when needed.
Paginator displays hits and manages the pagination logic.
The setupConnections method establishes the connections between these components to make them work together seamlessly.The central part of your search experience is the Searcher.
The Searcher performs a and obtains search results.
Most InstantSearch components connected to the Searcher.In this tutorial you’re targeting one index, so instantiate a HitsSearcher with the proper credentials.Create a new MainViewModel.kt file and add the following:
Kotlin
Report incorrect code
Copy
class MainViewModel : ViewModel() { private val searcher = HitsSearcher( applicationID = "latency", apiKey = "1f6fd3a6fb973cb08419fe7d288fa4db", indexName = "instant_search" ) // Search box val searchBoxState = SearchBoxState() val searchBoxConnector = SearchBoxConnector(searcher) // Hits val hitsPaginator = Paginator(searcher) { it.deserialize(Product.serializer()) } val connections = ConnectionHandler(searchBoxConnector) init { connections += searchBoxConnector.connectView(searchBoxState) connections += searchBoxConnector.connectPaginator(hitsPaginator) } override fun onCleared() { super.onCleared() searcher.cancel() }}
Most InstantSearch components should connect and disconnect in accordance to the Android Lifecycle to avoid memory leaks.
A ConnectionHandler handles a set of Connection s for you: each += call with a component implementing the Connection interface connects it and makes it active. Whenever you want to free resources or deactivate a component, call the disconnect method.
Get an instance of your ViewModel in your MainActivity by adding the following:
Kotlin
Report incorrect code
Copy
class MainActivity : ComponentActivity() { val viewModel: MainViewModel by viewModels() //...}
A ViewModel is a good place to put your data sources. This way, the data persists during configuration changes.
Create a SearchScreen.kt file that holds the search UI.
Add a composable function ProductsList to display a list of products, the hit row represented by a column with a Text presenting the name of the item and a Divider:
To make the search experience more user-friendly, you can give more context about the search results to your users.
You can do this with different InstantSearch modules.
First, add a statistics component.
This component shows the hit count and the request processing time.
This gives users a complete understanding of their search, without the need for extra interaction.
The StatsConnector extracts the metadata from the search response, and provides an interface to present it to users.
Add the StatsConnector to the MainViewModel and connect it to the Searcher.
Kotlin
Report incorrect code
Copy
class MainViewModel : ViewModel() { //... val statsText = StatsTextState() val statsConnector = StatsConnector(searcher) val connections = ConnectionHandler(searchBoxConnector, statsConnector) init { //... connections += statsConnector.connectView(statsText, DefaultStatsPresenter()) }}
The StatsConnector receives the search statistics now, but doesn’t display it yet.
Create a new composable Stats:
With your app, you can search more than 10,000 products. But, you don’t want to scroll to the bottom of the list to find the exact product you’re looking for.
One can more accurately filter the results by making use of the FilterListConnector components.
This section explains how to build a filter that allows to filter products by their category. First, add a FilterState component to the MainViewModel.
This component provides a convenient way to manage the state of your filters. Add the manufacturer refinement attribute.
Next, add the FilterListConnector, which stores the list of facets retrieved with search result. Add the connections between HitsSearcher, FilterState and FilterListConnector.
Create the FacetRow composable to display a facet.
The row represented by a column with two Text s for the facet value and count,
plus an Icon to display a checkmark for selected facets:
Update Search in MainActivity to include the instance of FacetListState from your MainViewModel:
Kotlin
Report incorrect code
Copy
class MainActivity : ComponentActivity() { private val viewModel: MainViewModel by viewModels() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { SearchAppTheme { Search( searchBoxState = viewModel.searchBoxState, paginator = viewModel.hitsPaginator, statsText = viewModel.statsText, facetList = viewModel.facetList ) } } }}
Rebuild your app. Now you see a filter button on top right of your screen. Click it to show the refinements list and select one or more refinements.
Dismiss the refinements list to see the changes happening live to your hits.
Highlighting enhances the user experience by putting emphasis on the parts of the result that match the query. It’s a visual indication of why a result is relevant to the query.You can add highlighting by implementing the Highlightable interface on Product.Define a highlightedName field to retrieve the highlighted value for the name attribute.
Kotlin
Report incorrect code
Copy
import com.algolia.instantsearch.core.Indexable@Serializabledata class Product( val name: String, override val objectID: String, override val _highlightResult: JsonObject?) : Indexable, Highlightable { val highlightedName: HighlightedString? get() = getHighlight("name")}
Use the .toAnnotatedString() extension function to convert an HighlightedString into a AnnotatedString assignable to a Text to display the highlighted names.
You now have a fully working search experience:
your users can search for products,
refine their results,
and understand how many records are returned and why they’re relevant to the query.Find the full source code in the GitHub repository.