Concepts / Building Search UI / Algolia Places
Aug. 20, 2019

Algolia Places

Using Places on Android

You can use Algolia Places on Android by creating your own SearcherPlaces.

This is the recommended implementation, implementing the Searcher interface and leveraging our ClientPlaces:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
class SearcherPlaces(
    val client: ClientPlaces = ClientPlaces(),
    val language: Language = Language.English,
    val query: PlacesQuery = PlacesQuery(),
    val requestOptions: RequestOptions? = null,
    override val coroutineScope: CoroutineScope = SearcherScope(),
    override val dispatcher: CoroutineDispatcher = Dispatchers.Main
) : Searcher<ResponseSearchPlacesMono> {

    private val sequencer = Sequencer()

    override val isLoading = SubscriptionValue(false)
    override val error = SubscriptionValue<Throwable?>(null)
    override val response = SubscriptionValue<ResponseSearchPlacesMono?>(null)

    private val exceptionHandler = CoroutineExceptionHandler { _, throwable ->
        error.value = throwable
    }

    override fun setQuery(text: String?) {
        query.query = text
    }

    override suspend fun search(): ResponseSearchPlacesMono {
        withContext(dispatcher) { isLoading.value = true }
        val response = client.searchPlaces(language, query, requestOptions)
        withContext(dispatcher) {
            isLoading.value = false
            this@SearcherPlaces.response.value = response
        }
        return response
    }

    override fun searchAsync(): Job {
        return coroutineScope.launch(dispatcher + exceptionHandler) {
            withContext(Dispatchers.Default) { search() }
        }.also {
            sequencer.addOperation(it)
        }
    }

    override fun cancel() {
        sequencer.cancelAll()
    }
}

Now we have a Searcher to find Places, we need to show search results.

Create a RecyclerView.Adapter and ViewHolder to display the results in a list:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
class PlacesViewHolder(val view: View) : RecyclerView.ViewHolder(view) {

    fun bind(place: PlaceLanguage) {
        println(place.highlightResultOrNull)
        val name = place.highlightResultOrNull
            ?.toHighlights("locale_names")
            ?.firstOrNull()
            ?.tokenize() ?: place.localNames.first()
        val county = place.highlightResultOrNull
            ?.toHighlights("county")
            ?.firstOrNull()
            ?.tokenize() ?: place.county.first()
        val postCode = place.postCodeOrNull?.firstOrNull()?.let { ", $it" } ?: ""

        view.placeName.text = TextUtils.concat(name, ", ", county, postCode)
    }

    fun HighlightResult.tokenize(): SpannedString {
        return HighlightTokenizer()(value).toSpannedString()
    }
}

class PlacesAdapter : ListAdapter<PlaceLanguage, PlacesViewHolder>(PlacesAdapter), HitsView<PlaceLanguage> {

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): PlacesViewHolder {
        return PlacesViewHolder(parent.inflate(R.layout.place_item))
    }

    override fun onBindViewHolder(holder: PlacesViewHolder, position: Int) {
        val item = getItem(position)

        if (item != null) holder.bind(item)
    }

    override fun setHits(hits: List<PlaceLanguage>) {
        submitList(hits)
    }

    companion object : DiffUtil.ItemCallback<PlaceLanguage>() {

        override fun areItemsTheSame(oldItem: PlaceLanguage, newItem: PlaceLanguage): Boolean {
            return oldItem::class == newItem::class
        }

        override fun areContentsTheSame(oldItem: PlaceLanguage, newItem: PlaceLanguage): Boolean {
            return oldItem == newItem
        }
    }
}

Finally, create your activity with a SearchView and a RecyclerView to provide a search autocomplete experience:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
class PlacesActivity : AppCompatActivity() {

    val query = PlacesQuery(
        type = PlaceType.City,
        hitsPerPage = 10,
        aroundLatLngViaIP = false,
        countries = listOf(Country.France)
    )
    val searcher = SearcherPlaces(query = query, language = Language.English)
    val searchBox = SearchBoxConnector(searcher)
    val adapter = PlacesAdapter()
    val connection = ConnectionHandler(searchBox)

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.places_activity)

        connection += searchBox.connectView(SearchBoxViewAppCompat(searchView))
        connection += searcher.connectHitsView(adapter) { hits -> hits.hits }

        placesList.let {
            it.itemAnimator = null
            it.adapter = adapter
            it.layoutManager = LinearLayoutManager(this)
            it.autoScrollToStart(adapter)
        }

        searcher.searchAsync()
    }

    override fun onDestroy() {
        super.onDestroy()
        connection.disconnect()
    }
}

Did you find this page helpful?