Concepts / Building Search UI / Customize an existing widget
Apr. 17, 2019

Customize an Existing Widget

Highlight and snippet your search results

Visually highlighting the search result is an essential feature of a great search interface. It will help your users understand your results by showing them why a result is relevant to their query.

Snippeting lets you display only a relevant excerpt of a text attribute that matches the query. Find out more in the Snippeting Guide.

Example

Using the Hits widget

See the Hits widget’s section on Highlighting.

Using the Highlighter

If you use custom hit views in your Hits, or to highlight results received by your custom widget, you should use the Highlighter. This tool will let you build a highlighted Spannablefrom a search result and an optional highlight color:

1
2
3
4
5
6
7
final Spannable highlightedAttribute = Highlighter.getDefault()
                                                  .setInput(result, attributeToHighlight)       // EITHER using Algolia's _highlightResult
                                                  .setInput("My <em> highlighted</em> string")  // OR using a raw markup string
                                                  .setColor(context)                  // EITHER using the default R.color.colorHighlighting
                                                  .setColor(Color.RED)                // OR using a system Color int
                                                  .setColor(R.color.myColor, context) // OR using a ColorRes
                                                  .render();

The default Highlighter will highlight anything between <em> and </em>. You can configure the Highlighter to highlight between any pair of terms with Highlighter.setDefault(newPrefix, newSuffix), or use a RegExp pattern to highlight any captured part with Highlighter.setDefault(newPattern).

See for example the e-commerce app’s CategoryOrTypeView, a TextView which takes either the category or the type attribute of a record and highlights it before displaying it.

Style your widgets

All the widgets found in the InstantSearch library inherit from standard View UI components: Hits is a specialized RecyclerView, SearchBox a specialized SearchView, etc.

Therefore, you’re able to customize the look and feel of your widgets the same way you would customize View components.

Translate your widgets

Adapting your InstantSearch application to a multilingual or international audience is done like any other Android application: by providing default Resources and alternative ones for each locale/language you want to offer.

For example, here’s how your Stats widget can display error messages either in english or in french:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<!-- activity.xml -->
<com.algolia.instantsearch.ui.views.Stats
    android:id="@android:id/empty"
    android:paddingTop="8dp"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_below="@id/box_filter"
    android:visibility="gone"
    algolia:autoHide="false"
    algolia:resultTemplate="@string/no_result_template" />

<!-- values/strings.xml: -->
<string name="no_result_template">No result.</string>

<!-- values-fr/strings.xml: -->
<string name="no_result_template">Aucun résultat.</string>

Learn more in the official Android Guide for Localizing your app.

Modify the list of items in widgets

Some widgets will display lists (e.g. Hits displays several results, RefinementList displaying several facet refinements, etc).

In any case, your widget should implement AlgoliaResultsListener, which will make it receive SearchResults on every request. This lets you manipulate and store hits, facets or other lists, which you can then display as you want.

Apply default value to widgets

You might want your widget to apply a default value when added to your search interface; for example a RefinementList will add the attribute it refines to the Searcher’s facets.

You can do so by having your widget implement AlgoliaSearcherListener, with which it will receive a Searcher reference within initWithSearcher:

1
2
3
4
5
@Override
public void initWithSearcher(@NonNull Searcher searcher) {
    this.searcher.addFacet(attribute); // Apply default refinement
    this.searcher = searcher; // You can also store the Searcher for later use
}

You can then use all the Searcher API to modify the search interface’s state from your widget.

How to provide search parameters

Algolia offers several search-time parameters. If one of the parameters you want to use is not covered by a Widget or ViewModel, then you can use the Searcher’s query to set parameters at search time.

Basic Search Parameters

All Search API parameters are available through properties of Searcher.query. For example, in order to modify the attributesToRetrieve, you can do the following:

1
searcher.setQuery(searcher.getQuery().setAttributesToRetrieve("att1", "att2"));

Filtering

Facets

The Searcher maintains a list of refined values for every facet, called facet refinements. A facet refinement is the combination of an attribute name and a value. The refinements can be conjunctive (keeping only results that match all refinements) or disjunctive (keeping results that match any refinement).

To edit the refinements, use the facet refinement handling methods, like addFacetRefinement(name, value), removeFacetRefinement(name, value) and updateFacetRefinement(name, value, isActive). For example:

1
2
3
4
5
6
7
8
// add a new refinement
searcher.addFacetRefinement("company", "Algolia");

// make it inactive
searcher.updateFacetRefinement("company", "Algolia", false);

// remove it
searcher.removeFacetRefinement("company", "Algolia");

A given facet can be treated as either conjunctive (the default—refinements combined with an AND operator) or disjunctive (refinements combined with an OR). You can modify the conjunctive/disjunctive status of a facet by calling setFacet(name, isDisjunctive).

When a search is triggered, the searcher will build the facet filters according to the refinements and the conjunctive/disjunctive state of each facet.

Note: you still need to specify the list of all facets using searcher.addFacet().

Note: The filters and facetFilters search parameters will be overridden by the facet refinements: any manually specified facet value will be lost.

Numeric filters

The search parameters also provide tools to easily manipulate numeric filters, through the notion of numeric refinements. A numeric refinement is made up of an attribute name (the left operand), a comparison operator, and a value (right operand).

The numeric refinement handling methods work in a very similar fashion to the facet refinements (see above):

  • Numeric refinements are edited via addNumericRefinement(...) and removeNumericRefinement(...). For example:
1
2
searcher.addNumericRefinement(
    new NumericRefinement("price", NumericRefinement.OPERATOR_GE,  10);

Note: The filters and numericFilters search parameters will be overridden by the numeric refinements: any manually specified value will be lost.

Customize the complete UI of the widgets

InstantSearch Android provides widgets out of the box, which let you add features to your application easily. These widgets provide you several customization options, but sometimes this is not enough. For those cases, InstantSearch Android provides a lower level API by exposing its Searcher and some ViewModels.

Using the Searcher

The Searcher is the main component of InstantSearch Android. It wraps an Algolia API Client and provides a level of abstraction over it.

The Searcher is responsible of all search requests: when Searcher#search() is called, the Searcher will fire a request with the current query, and will forward the search results to its listeners.

Listeners

A listener is an object implementing one or more of the following interfaces, that will be called after each search request:

You can add a listener to a Searcher by calling Searcher#register{Results,Error}Listener.

To avoid leaking memory, you should call Searcher#destroy() in your Activity/Fragment’s onDestroy method to release existing listeners.

Programmatic API

The Searcher provides a programmatic API to handle several aspects of your search interface:

  • Search: search, setIndex, setQuery
  • Filtering and Faceting: {add,remove,delete}Facet, {add,has,remove,update}FacetRefinement, clearFacetRefinements, {add,get,remove}BooleanFilter, get{Updated}FacetStats
  • State handling (page, requests, …): hasMoreHits, hasPendingRequests, shouldLoadMore & loadMore, reset

You can have several Searcher instances in the same application, for example for doing multi-index search. You can get the default Searcher with get (or get a specific one by specifying the variant parameter), create one with its several create methods, delete a given one with destroy, or delete all existing instances with destroyAll.

Using ViewModels

We provide ViewModels that encapsulate some logic for a specific search concept and provide a way to interact with InstantSearch.

SearchBoxViewModel

This component handles the logic for linking any kind of Search input to one or several InstantSearch interface. You can use it to decouple the logic linking InstantSearch to your search input from the input’s actual look and feel.

See its usage in the multi-index search guide to link a single SearchBox to several InstantSearch interfaces.

Did you find this page helpful?