Skip to content
This repository was archived by the owner on May 24, 2022. It is now read-only.

Commit 888abd8

Browse files
committed
add query suggestion to android-view showcase
1 parent d841dfe commit 888abd8

File tree

17 files changed

+458
-10
lines changed

17 files changed

+458
-10
lines changed

guides/src/main/kotlin/com/algolia/instantsearch/guides/querysuggestion/QuerySuggestionViewModel.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ class QuerySuggestionViewModel : ViewModel() {
2323
val multiSearcher = MultiSearcher(client)
2424
val productSearcher = multiSearcher.addHitsSearcher(
2525
indexName = IndexName("STAGING_native_ecom_demo_products")
26-
) // TODO: native guide + guide screenshots
26+
)
2727
val suggestionSearcher = multiSearcher.addHitsSearcher(
2828
indexName = IndexName("STAGING_native_ecom_demo_products_query_suggestions")
2929
)

showcase/android-view/build.gradle

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -42,13 +42,13 @@ dependencies {
4242
implementation "com.algolia:instantsearch-android-paging3:$instantsearch"
4343
implementation "com.algolia:instantsearch-android-loading:$instantsearch"
4444

45-
implementation "androidx.lifecycle:lifecycle-extensions:$lifecycle_version"
46-
implementation "androidx.lifecycle:lifecycle-common-java8:$lifecycle_version"
47-
implementation "androidx.constraintlayout:constraintlayout:2.1.2"
48-
implementation "com.google.android.material:material:1.4.0"
45+
implementation "androidx.fragment:fragment-ktx:1.4.1"
46+
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.4.1"
47+
implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.4.1"
48+
implementation "androidx.constraintlayout:constraintlayout:2.1.3"
49+
implementation "com.google.android.material:material:1.5.0"
4950
implementation "com.github.bumptech.glide:glide:4.10.0"
5051
implementation "io.apptik.widget:multislider:1.3"
51-
implementation 'androidx.appcompat:appcompat:1.4.0'
5252
debugImplementation "com.squareup.leakcanary:leakcanary-android:$canary"
5353

5454
testImplementation 'junit:junit:4.12'

showcase/android-view/src/main/AndroidManifest.xml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,10 @@
120120
android:name="com.algolia.instantsearch.showcase.filter.facet.dynamic.DynamicFacetShowcase"
121121
android:parentActivityName=".directory.DirectoryShowcase" />
122122

123+
<activity
124+
android:name="com.algolia.instantsearch.showcase.suggestion.QuerySuggestionShowcase"
125+
android:parentActivityName=".directory.DirectoryShowcase" />
126+
123127
<activity
124128
android:name=".customdata.TemplateActivity"
125129
android:exported="true">

showcase/android-view/src/main/kotlin/com/algolia/instantsearch/showcase/Demo.kt

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,12 +26,12 @@ import androidx.core.text.bold
2626
import androidx.core.text.buildSpannedString
2727
import androidx.recyclerview.widget.LinearLayoutManager
2828
import androidx.recyclerview.widget.RecyclerView
29-
import com.algolia.instantsearch.core.connection.ConnectionHandler
30-
import com.algolia.instantsearch.core.searcher.Searcher
3129
import com.algolia.instantsearch.android.filter.clear.FilterClearViewImpl
3230
import com.algolia.instantsearch.android.list.autoScrollToStart
3331
import com.algolia.instantsearch.android.searchbox.SearchBoxViewAppCompat
3432
import com.algolia.instantsearch.android.stats.StatsTextViewSpanned
33+
import com.algolia.instantsearch.core.connection.ConnectionHandler
34+
import com.algolia.instantsearch.core.searcher.Searcher
3535
import com.algolia.instantsearch.filter.clear.FilterClearConnector
3636
import com.algolia.instantsearch.filter.clear.connectView
3737
import com.algolia.instantsearch.filter.state.FilterGroupID
@@ -179,6 +179,16 @@ fun AppCompatActivity.configureRecyclerView(
179179
}
180180
}
181181

182+
fun RecyclerView.configure(
183+
recyclerViewAdapter: RecyclerView.Adapter<*>
184+
) {
185+
visibility = View.VISIBLE
186+
layoutManager = LinearLayoutManager(context)
187+
adapter = recyclerViewAdapter
188+
itemAnimator = null
189+
autoScrollToStart(recyclerViewAdapter)
190+
}
191+
182192
val Intent.indexName: IndexName get() = IndexName(extras!!.getString(KeyIndexName)!!)
183193

184194
fun <R> AppCompatActivity.configureSearchBox(
@@ -244,11 +254,21 @@ public fun Set<FilterGroup<*>>.highlight(
244254
val string = converter(setOf(group))
245255

246256
it.append(string)
247-
it.setSpan(ForegroundColorSpan(color), begin, it.length, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
257+
it.setSpan(
258+
ForegroundColorSpan(color),
259+
begin,
260+
it.length,
261+
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE
262+
)
248263
if (index < size - 1) {
249264
begin = it.length
250265
it.append(" AND ")
251-
it.setSpan(StyleSpan(Typeface.BOLD), begin, it.length, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
266+
it.setSpan(
267+
StyleSpan(Typeface.BOLD),
268+
begin,
269+
it.length,
270+
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE
271+
)
252272
}
253273
begin = it.length
254274
}

showcase/android-view/src/main/kotlin/com/algolia/instantsearch/showcase/directory/Directory.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import com.algolia.instantsearch.showcase.search.SearchAutoCompleteTextView
2828
import com.algolia.instantsearch.showcase.search.SearchOnSubmitShowcase
2929
import com.algolia.instantsearch.showcase.sortby.SortByShowcase
3030
import com.algolia.instantsearch.showcase.stats.StatsShowcase
31+
import com.algolia.instantsearch.showcase.suggestion.QuerySuggestionShowcase
3132
import com.algolia.search.model.ObjectID
3233

3334
val showcases = mapOf(
@@ -60,4 +61,5 @@ val showcases = mapOf(
6061
ObjectID("query_rule_custom_data") to QueryRuleCustomDataShowcase::class,
6162
ObjectID("filter_rating") to RatingShowcase::class,
6263
ObjectID("dynamic_facets") to DynamicFacetShowcase::class,
64+
ObjectID("query_suggestions") to QuerySuggestionShowcase::class,
6365
)
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package com.algolia.instantsearch.showcase.list.suggestion
2+
3+
import com.algolia.instantsearch.core.highlighting.HighlightedString
4+
import com.algolia.instantsearch.highlighting.Highlightable
5+
import com.algolia.search.model.Attribute
6+
import com.algolia.search.model.ObjectID
7+
import com.algolia.search.model.indexing.Indexable
8+
import kotlinx.serialization.Serializable
9+
import kotlinx.serialization.json.JsonObject
10+
11+
@Serializable
12+
data class Suggestion(
13+
val query: String,
14+
override val objectID: ObjectID,
15+
override val _highlightResult: JsonObject?
16+
) : Indexable, Highlightable {
17+
18+
val highlightedQuery: HighlightedString?
19+
get() = getHighlight(Attribute("query"))
20+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
package com.algolia.instantsearch.showcase.suggestion
2+
3+
import android.os.Bundle
4+
import androidx.activity.viewModels
5+
import androidx.appcompat.app.AppCompatActivity
6+
import androidx.appcompat.widget.SearchView
7+
import androidx.fragment.app.commit
8+
import androidx.fragment.app.replace
9+
import com.algolia.instantsearch.android.searchbox.SearchBoxViewAppCompat
10+
import com.algolia.instantsearch.core.connection.ConnectionHandler
11+
import com.algolia.instantsearch.searchbox.connectView
12+
import com.algolia.instantsearch.showcase.R
13+
import com.algolia.instantsearch.showcase.suggestion.product.ProductFragment
14+
import com.algolia.instantsearch.showcase.suggestion.suggestion.SuggestionFragment
15+
16+
class QuerySuggestionShowcase : AppCompatActivity() {
17+
18+
private val viewModel by viewModels<QuerySuggestionViewModel>()
19+
private val connection = ConnectionHandler()
20+
21+
override fun onCreate(savedInstanceState: Bundle?) {
22+
super.onCreate(savedInstanceState)
23+
setContentView(R.layout.showcase_query_suggestion)
24+
25+
// Setup search box
26+
val searchView = findViewById<SearchView>(R.id.searchView)
27+
val searchBoxView = SearchBoxViewAppCompat(searchView)
28+
connection += viewModel.searchBox.connectView(searchBoxView)
29+
30+
// Switch fragments on search box focus
31+
searchView.setOnQueryTextFocusChangeListener { _, hasFocus ->
32+
if (hasFocus) showSuggestions() else showProducts()
33+
}
34+
35+
// Observe suggestions
36+
viewModel.suggestions.observe(this) { searchBoxView.setText(it.query, true) }
37+
38+
// Initially show products view
39+
showProducts()
40+
}
41+
42+
/** display suggestions fragment */
43+
private fun showSuggestions() {
44+
supportFragmentManager.commit {
45+
replace<SuggestionFragment>(R.id.container)
46+
setReorderingAllowed(true)
47+
}
48+
}
49+
50+
/** display products fragment */
51+
private fun showProducts() {
52+
supportFragmentManager.commit {
53+
replace<ProductFragment>(R.id.container)
54+
setReorderingAllowed(true)
55+
}
56+
}
57+
58+
override fun onDestroy() {
59+
super.onDestroy()
60+
connection.clear()
61+
}
62+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
package com.algolia.instantsearch.showcase.suggestion
2+
3+
import androidx.lifecycle.MutableLiveData
4+
import androidx.lifecycle.ViewModel
5+
import com.algolia.instantsearch.core.connection.ConnectionHandler
6+
import com.algolia.instantsearch.searchbox.SearchBoxConnector
7+
import com.algolia.instantsearch.searcher.hits.addHitsSearcher
8+
import com.algolia.instantsearch.searcher.multi.MultiSearcher
9+
import com.algolia.instantsearch.showcase.list.suggestion.Suggestion
10+
import com.algolia.search.client.ClientSearch
11+
import com.algolia.search.model.APIKey
12+
import com.algolia.search.model.ApplicationID
13+
import com.algolia.search.model.IndexName
14+
import io.ktor.client.features.logging.*
15+
16+
class QuerySuggestionViewModel : ViewModel() {
17+
18+
private val client = ClientSearch(
19+
applicationID = ApplicationID("latency"),
20+
apiKey = APIKey("927c3fe76d4b52c5a2912973f35a3077"),
21+
logLevel = LogLevel.ALL
22+
)
23+
val multiSearcher = MultiSearcher(client)
24+
val productSearcher = multiSearcher.addHitsSearcher(
25+
indexName = IndexName("STAGING_native_ecom_demo_products")
26+
)
27+
val suggestionSearcher = multiSearcher.addHitsSearcher(
28+
indexName = IndexName("STAGING_native_ecom_demo_products_query_suggestions")
29+
)
30+
val searchBox = SearchBoxConnector(multiSearcher)
31+
private val connection = ConnectionHandler(searchBox)
32+
33+
val suggestions = MutableLiveData<Suggestion>()
34+
35+
override fun onCleared() {
36+
multiSearcher.cancel()
37+
connection.clear()
38+
client.close()
39+
}
40+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package com.algolia.instantsearch.showcase.suggestion.product
2+
3+
import com.algolia.instantsearch.core.highlighting.HighlightedString
4+
import com.algolia.instantsearch.highlighting.Highlightable
5+
import com.algolia.search.model.Attribute
6+
import com.algolia.search.model.ObjectID
7+
import com.algolia.search.model.indexing.Indexable
8+
import kotlinx.serialization.SerialName
9+
import kotlinx.serialization.Serializable
10+
import kotlinx.serialization.json.JsonObject
11+
12+
@Serializable
13+
data class Product(
14+
val name: String,
15+
@SerialName("image_urls") val images: List<String>,
16+
val price: Price,
17+
val description: String,
18+
override val objectID: ObjectID,
19+
override val _highlightResult: JsonObject?
20+
) : Indexable, Highlightable {
21+
22+
val highlightedName: HighlightedString?
23+
get() = getHighlight(Attribute("name"))
24+
}
25+
26+
@Serializable
27+
data class Price(
28+
val currency: String,
29+
val value: String,
30+
)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
package com.algolia.instantsearch.showcase.suggestion.product
2+
3+
import android.view.View
4+
import android.view.ViewGroup
5+
import android.widget.TextView
6+
import androidx.recyclerview.widget.DiffUtil
7+
import androidx.recyclerview.widget.ListAdapter
8+
import androidx.recyclerview.widget.RecyclerView
9+
import com.algolia.instantsearch.android.highlighting.toSpannedString
10+
import com.algolia.instantsearch.android.inflate
11+
import com.algolia.instantsearch.core.hits.HitsView
12+
import com.algolia.instantsearch.showcase.R
13+
import com.algolia.instantsearch.showcase.suggestion.product.ProductAdapter.ProductViewHolder
14+
import com.bumptech.glide.Glide
15+
16+
class ProductAdapter : ListAdapter<Product, ProductViewHolder>(ProductDiffUtil), HitsView<Product> {
17+
18+
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) =
19+
ProductViewHolder(parent.inflate(R.layout.list_item_large))
20+
21+
override fun onBindViewHolder(holder: ProductViewHolder, position: Int) =
22+
holder.bind(getItem(position))
23+
24+
override fun setHits(hits: List<Product>) = submitList(hits)
25+
26+
class ProductViewHolder(private val view: View) : RecyclerView.ViewHolder(view) {
27+
28+
fun bind(item: Product) {
29+
view.findViewById<TextView>(R.id.itemTitle).text =
30+
item.highlightedName?.toSpannedString() ?: item.name
31+
view.findViewById<TextView>(R.id.itemSubtitle).text = item.price.value
32+
Glide
33+
.with(view.context)
34+
.load(item.images.first())
35+
.into(view.findViewById(R.id.itemImage))
36+
}
37+
}
38+
39+
private object ProductDiffUtil : DiffUtil.ItemCallback<Product>() {
40+
override fun areItemsTheSame(oldItem: Product, newItem: Product) =
41+
oldItem.objectID == newItem.objectID
42+
43+
override fun areContentsTheSame(oldItem: Product, newItem: Product) = oldItem == newItem
44+
}
45+
}

0 commit comments

Comments
 (0)