diff --git a/examples/android/src/main/AndroidManifest.xml b/examples/android/src/main/AndroidManifest.xml
index 77736ee1d..c07026502 100644
--- a/examples/android/src/main/AndroidManifest.xml
+++ b/examples/android/src/main/AndroidManifest.xml
@@ -7,8 +7,8 @@
+
+
,
+ val price: Price,
+ val description: String,
+ override val objectID: ObjectID,
+ override val _highlightResult: JsonObject?
+) : Indexable, Highlightable {
+
+ val highlightedName: HighlightedString?
+ get() = getHighlight(Attribute("name"))
+}
+
+@Serializable
+data class Price(
+ val currency: String,
+ val value: String,
+)
diff --git a/examples/android/src/main/kotlin/com/algolia/instantsearch/examples/android/guides/querysuggestion/ProductAdapter.kt b/examples/android/src/main/kotlin/com/algolia/instantsearch/examples/android/guides/querysuggestion/ProductAdapter.kt
new file mode 100644
index 000000000..4c9160371
--- /dev/null
+++ b/examples/android/src/main/kotlin/com/algolia/instantsearch/examples/android/guides/querysuggestion/ProductAdapter.kt
@@ -0,0 +1,41 @@
+package com.algolia.instantsearch.examples.android.guides.querysuggestion
+
+import android.view.View
+import android.view.ViewGroup
+import android.widget.ImageView
+import android.widget.TextView
+import androidx.recyclerview.widget.DiffUtil
+import androidx.recyclerview.widget.ListAdapter
+import androidx.recyclerview.widget.RecyclerView
+import coil.load
+import com.algolia.instantsearch.android.highlighting.toSpannedString
+import com.algolia.instantsearch.android.inflate
+import com.algolia.instantsearch.core.hits.HitsView
+import com.algolia.instantsearch.examples.android.R
+import com.algolia.instantsearch.examples.android.guides.querysuggestion.ProductAdapter.ProductViewHolder
+
+class ProductAdapter : ListAdapter(ProductDiffUtil), HitsView {
+
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) =
+ ProductViewHolder(parent.inflate(R.layout.list_item_large))
+
+ override fun onBindViewHolder(holder: ProductViewHolder, position: Int) =
+ holder.bind(getItem(position))
+
+ override fun setHits(hits: List) = submitList(hits)
+
+ class ProductViewHolder(private val view: View) : RecyclerView.ViewHolder(view) {
+
+ fun bind(item: Product) {
+ view.findViewById(R.id.itemTitle).text =
+ item.highlightedName?.toSpannedString() ?: item.name
+ view.findViewById(R.id.itemSubtitle).text = item.price.value
+ view.findViewById(R.id.itemImage).load(item.images.first())
+ }
+ }
+
+ private object ProductDiffUtil : DiffUtil.ItemCallback() {
+ override fun areItemsTheSame(oldItem: Product, newItem: Product) = oldItem.objectID == newItem.objectID
+ override fun areContentsTheSame(oldItem: Product, newItem: Product) = oldItem == newItem
+ }
+}
diff --git a/examples/android/src/main/kotlin/com/algolia/instantsearch/examples/android/guides/querysuggestion/ProductFragment.kt b/examples/android/src/main/kotlin/com/algolia/instantsearch/examples/android/guides/querysuggestion/ProductFragment.kt
new file mode 100644
index 000000000..f8686f0e3
--- /dev/null
+++ b/examples/android/src/main/kotlin/com/algolia/instantsearch/examples/android/guides/querysuggestion/ProductFragment.kt
@@ -0,0 +1,38 @@
+package com.algolia.instantsearch.examples.android.guides.querysuggestion
+
+import android.os.Bundle
+import android.view.View
+import androidx.fragment.app.Fragment
+import androidx.fragment.app.activityViewModels
+import androidx.recyclerview.widget.RecyclerView
+import com.algolia.instantsearch.core.connection.ConnectionHandler
+import com.algolia.instantsearch.core.hits.connectHitsView
+import com.algolia.instantsearch.examples.android.R
+import com.algolia.instantsearch.examples.android.guides.extension.configure
+import com.algolia.search.helper.deserialize
+
+class ProductFragment : Fragment(R.layout.fragment_items) {
+
+ private val viewModel: QuerySuggestionViewModel by activityViewModels()
+ private val connection = ConnectionHandler()
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+
+ // Configure products view
+ val productAdapter = ProductAdapter()
+ view.findViewById(R.id.items)
+ .configure(productAdapter) // Configure the RecyclerView with the adapter
+ connection += viewModel.productSearcher.connectHitsView(productAdapter) {
+ it.hits.deserialize(Product.serializer())
+ }
+
+ // Run initial search
+ viewModel.productSearcher.searchAsync()
+ }
+
+ override fun onDestroyView() {
+ super.onDestroyView()
+ connection.clear()
+ }
+}
diff --git a/examples/android/src/main/kotlin/com/algolia/instantsearch/examples/android/guides/querysuggestion/QuerySuggestionActivity.kt b/examples/android/src/main/kotlin/com/algolia/instantsearch/examples/android/guides/querysuggestion/QuerySuggestionActivity.kt
new file mode 100644
index 000000000..5af1c197b
--- /dev/null
+++ b/examples/android/src/main/kotlin/com/algolia/instantsearch/examples/android/guides/querysuggestion/QuerySuggestionActivity.kt
@@ -0,0 +1,60 @@
+package com.algolia.instantsearch.examples.android.guides.querysuggestion
+
+import android.os.Bundle
+import androidx.activity.viewModels
+import androidx.appcompat.app.AppCompatActivity
+import androidx.appcompat.widget.SearchView
+import androidx.fragment.app.commit
+import androidx.fragment.app.replace
+import com.algolia.instantsearch.android.searchbox.SearchBoxViewAppCompat
+import com.algolia.instantsearch.core.connection.ConnectionHandler
+import com.algolia.instantsearch.examples.android.R
+import com.algolia.instantsearch.searchbox.connectView
+
+class QuerySuggestionActivity : AppCompatActivity() {
+
+ private val viewModel by viewModels()
+ private val connection = ConnectionHandler()
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContentView(R.layout.activity_query_suggestion)
+
+ // Setup search box
+ val searchView = findViewById(R.id.searchView)
+ val searchBoxView = SearchBoxViewAppCompat(searchView)
+ connection += viewModel.searchBox.connectView(searchBoxView)
+
+ // Switch fragments on search box focus
+ searchView.setOnQueryTextFocusChangeListener { _, hasFocus ->
+ if (hasFocus) showSuggestions() else showProducts()
+ }
+
+ // Observe suggestions
+ viewModel.suggestions.observe(this) { searchBoxView.setText(it.query, true) }
+
+ // Initially show products view
+ showProducts()
+ }
+
+ private fun showSuggestions() {
+ supportFragmentManager.commit {
+ replace(R.id.container)
+ setReorderingAllowed(true)
+ addToBackStack("suggestions") // name can be null
+ }
+ }
+
+ private fun showProducts() {
+ supportFragmentManager.commit {
+ replace(R.id.container)
+ setReorderingAllowed(true)
+ addToBackStack("products") // name can be null
+ }
+ }
+
+ override fun onDestroy() {
+ super.onDestroy()
+ connection.clear()
+ }
+}
diff --git a/examples/android/src/main/kotlin/com/algolia/instantsearch/examples/android/guides/querysuggestion/QuerySuggestionViewModel.kt b/examples/android/src/main/kotlin/com/algolia/instantsearch/examples/android/guides/querysuggestion/QuerySuggestionViewModel.kt
new file mode 100644
index 000000000..c44bd1e17
--- /dev/null
+++ b/examples/android/src/main/kotlin/com/algolia/instantsearch/examples/android/guides/querysuggestion/QuerySuggestionViewModel.kt
@@ -0,0 +1,41 @@
+package com.algolia.instantsearch.examples.android.guides.querysuggestion
+
+import androidx.lifecycle.MutableLiveData
+import androidx.lifecycle.ViewModel
+import com.algolia.instantsearch.core.connection.ConnectionHandler
+import com.algolia.instantsearch.examples.android.guides.model.Suggestion
+import com.algolia.instantsearch.searchbox.SearchBoxConnector
+import com.algolia.instantsearch.searcher.hits.addHitsSearcher
+import com.algolia.instantsearch.searcher.multi.MultiSearcher
+import com.algolia.search.client.ClientSearch
+import com.algolia.search.logging.LogLevel
+import com.algolia.search.model.APIKey
+import com.algolia.search.model.ApplicationID
+import com.algolia.search.model.IndexName
+
+class QuerySuggestionViewModel : ViewModel() {
+
+ private val client = ClientSearch(
+ applicationID = ApplicationID("latency"),
+ apiKey = APIKey("927c3fe76d4b52c5a2912973f35a3077"),
+ logLevel = LogLevel.All
+ )
+ val multiSearcher = MultiSearcher(client)
+ val productSearcher = multiSearcher.addHitsSearcher(indexName = IndexName("STAGING_native_ecom_demo_products"))
+ val suggestionSearcher =
+ multiSearcher.addHitsSearcher(indexName = IndexName("STAGING_native_ecom_demo_products_query_suggestions"))
+ val searchBox = SearchBoxConnector(multiSearcher)
+ val suggestions = MutableLiveData()
+ val connections = ConnectionHandler()
+
+ init {
+ searchBox.connect()
+ }
+
+ override fun onCleared() {
+ searchBox.disconnect()
+ connections.clear()
+ multiSearcher.cancel()
+ client.close()
+ }
+}
diff --git a/examples/android/src/main/kotlin/com/algolia/instantsearch/examples/android/guides/querysuggestion/SuggestionAdapter.kt b/examples/android/src/main/kotlin/com/algolia/instantsearch/examples/android/guides/querysuggestion/SuggestionAdapter.kt
new file mode 100644
index 000000000..19a33b8c6
--- /dev/null
+++ b/examples/android/src/main/kotlin/com/algolia/instantsearch/examples/android/guides/querysuggestion/SuggestionAdapter.kt
@@ -0,0 +1,45 @@
+package com.algolia.instantsearch.examples.android.guides.querysuggestion
+
+import android.view.View
+import android.view.ViewGroup
+import android.widget.TextView
+import androidx.recyclerview.widget.DiffUtil
+import androidx.recyclerview.widget.ListAdapter
+import androidx.recyclerview.widget.RecyclerView
+import com.algolia.instantsearch.android.highlighting.toSpannedString
+import com.algolia.instantsearch.android.inflate
+import com.algolia.instantsearch.core.hits.HitsView
+import com.algolia.instantsearch.examples.android.R
+import com.algolia.instantsearch.examples.android.guides.model.Suggestion
+import com.algolia.instantsearch.examples.android.guides.querysuggestion.SuggestionAdapter.SuggestionViewHolder
+
+class SuggestionAdapter(private val onSuggestionClick: ((Suggestion) -> Unit)) :
+ ListAdapter(SuggestionAdapter),
+ HitsView {
+
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) =
+ SuggestionViewHolder(parent.inflate(R.layout.list_item_suggestion))
+
+ override fun onBindViewHolder(holder: SuggestionViewHolder, position: Int) {
+ val item = getItem(position)
+ holder.bind(item, onSuggestionClick)
+ }
+
+ override fun setHits(hits: List) = submitList(hits)
+
+ class SuggestionViewHolder(private val view: View) : RecyclerView.ViewHolder(view) {
+
+ fun bind(item: Suggestion, onClick: ((Suggestion) -> Unit)) {
+ view.setOnClickListener { onClick(item) }
+ view.findViewById(R.id.itemName).text = item.highlightedQuery?.toSpannedString() ?: item.query
+ }
+ }
+
+ companion object : DiffUtil.ItemCallback() {
+ override fun areItemsTheSame(oldItem: Suggestion, newItem: Suggestion) =
+ oldItem.objectID == newItem.objectID
+
+ override fun areContentsTheSame(oldItem: Suggestion, newItem: Suggestion): Boolean =
+ oldItem == newItem
+ }
+}
diff --git a/examples/android/src/main/kotlin/com/algolia/instantsearch/examples/android/guides/querysuggestion/SuggestionFragment.kt b/examples/android/src/main/kotlin/com/algolia/instantsearch/examples/android/guides/querysuggestion/SuggestionFragment.kt
new file mode 100644
index 000000000..79473c9b3
--- /dev/null
+++ b/examples/android/src/main/kotlin/com/algolia/instantsearch/examples/android/guides/querysuggestion/SuggestionFragment.kt
@@ -0,0 +1,40 @@
+package com.algolia.instantsearch.examples.android.guides.querysuggestion
+
+import android.os.Bundle
+import android.view.View
+import androidx.fragment.app.Fragment
+import androidx.fragment.app.activityViewModels
+import androidx.recyclerview.widget.RecyclerView
+import com.algolia.instantsearch.core.connection.ConnectionHandler
+import com.algolia.instantsearch.core.hits.connectHitsView
+import com.algolia.instantsearch.examples.android.R
+import com.algolia.instantsearch.examples.android.guides.extension.configure
+import com.algolia.instantsearch.examples.android.guides.model.Suggestion
+import com.algolia.search.helper.deserialize
+
+class SuggestionFragment : Fragment(R.layout.fragment_items) {
+
+ private val viewModel: QuerySuggestionViewModel by activityViewModels()
+ private val connection = ConnectionHandler()
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+ // Configure suggestions view
+ val suggestionAdapter = SuggestionAdapter {
+ // On suggestion click, update the
+ viewModel.suggestions.value = it
+ }
+ view.findViewById(R.id.items).configure(suggestionAdapter) // Configure the RecyclerView with the adapter
+ connection += viewModel.suggestionSearcher.connectHitsView(suggestionAdapter) {
+ it.hits.deserialize(Suggestion.serializer())
+ }
+
+ // Run initial search
+ viewModel.suggestionSearcher.searchAsync()
+ }
+
+ override fun onDestroyView() {
+ super.onDestroyView()
+ connection.clear()
+ }
+}