Full-text search using Android AppSearch library and RxJava

Mikhail Valuyskiy
5 min readJul 15, 2021

An important part of mobile app is designing search feature. There are many cases when user need to search some local information in the app. More over there are many types of documents, so user can search not only one type, but all kind of local information such as movies info, music, books etc.

Photo by Shunya Koide on Unsplash

Android 12 introduces AppSearch, a high-performance on-device search engine, as a system service. AppSearch allows applications to index structured data and search over it with built-in full-text search capabilities. Furthermore, AppSearch supports native search features, like highly-efficient indexing and retrieval, multi-language support, and relevancy ranking.

So, in this article I would like to share the preview of using AppSearch. We will create demo app for searching movies and actors in the local storage using AppSearch library.

Intro

AppSearch provides the following features:

  • A fast, mobile-first storage implementation with low I/O use
  • Highly efficient indexing and querying over large data sets
  • Multi-language support, such as English and Spanish
  • Relevance ranking and usage scoring

Due to lower I/O use, AppSearch offers lower latency for indexing and searching over large datasets compared to SQLite. AppSearch simplifies cross-type queries by supporting single queries whereas SQLite merges results from multiple tables

Declaring dependencies

First of all, let’s add some dependencies. Open build.gradle and add AppSearch and Guava:

Create documents MovieDocument and PersonDocument

Before we start working with AppSearch we need to create models of data so called documents:

Let’s take a look to some annotations:

  • @Document.Namespace. Required field for a document class. All documents must have a namespace. This field is used for grouping same documents during search.
  • @Document.Id Required field for a document class.
  • @Document.Score Optional field for a document class, used to set the score of the document.
  • @Document.StringProperty Optional field for a document class, used to index a movie’s text for this document class.

Document.StringProperty has 2 parameters:

  • INDEXING_TYPE_PREFIXES — Content in this property should be returned for queries that are either exact matches or query matches of the tokens appearing in this property.
    Ex. A property with “fool” should match a query for “foo”.
  • INDEXING_TYPE_EXACT_TERMS. Content in this property should only be returned for queries matching the exact tokens appearing in this property. For example a property with “fool” should NOT match a query for “foo”.

The same way we create PersonDocument where we will keep information about actors.

Open a database and set a schema

Before searching the documents we need to create the database and set the scheme. The code below shows that we create database with “movies_demo_db” and return ListenableFuture

All methods which allow working with AppSearch are located in AppSearchRepository.kt. Here is shown setSchema() method:

Currently you should you ListenableFuture in order to complete the task.

Future, ListenableFuture, Executor

A traditional Future represents the result of an asynchronous computation: a computation that may or may not have finished producing a result yet. A Future can be a handle to an in-progress computation, a promise from a service to supply us with a result.

A ListenableFuture allows you to register callbacks to be executed once the computation is complete, or if the computation is already complete, immediately. This simple addition makes it possible to efficiently support many operations that the basic Future interface cannot support.

The basic operation added by ListenableFuture is addListener(Runnable, Executor), which specifies that when the computation represented by this Future is done, the specified Runnable will be run on the specified Executor. More details about ListenableFuture you can find here

Saving data to database

After creating database and set the scheme we ready to add some data to the database. First of all, it is necessary to create PutRequest using Builder:

Create ListenableFuture in order to save document and register callback for execution:

Here we will use Observable.create() in order to use RxJava and create chain of methods using RxJava.

Searching data and showing in UI

Before search we should create SearchSpec — this helps us to clarify the search request using Builder:

val searchSpec = SearchSpec.Builder().addFilterNamespaces(nameSpace) .build()

There are many arguments which you can use, let’s take a look:

addFilterNamespaces(nameSpace) — the app search will filter documents by given nameSpace. If nameSpace is empty — it will search all documents.

  • setResultCountPerPage() — Sets the number of results per page in the returned object. The default number of results per page is 10.
  • setRankingStrategy() —Sets ranking strategy for AppSearch results.
  • setOrder() — Indicates the order (ORDER_DESCENDING, ORDER_ASCENDING) of returned search results, the default is ORDER_DESCENDING, meaning that results with higher scores come first.
  • setResultGrouping() — Set the maximum number of results to return for each group, where groups are defined by grouping type

So, the search method now look’s like this:

SearchResult is an interface which allow get results using getNextPage()

public interface SearchResults extends Closeable { ListenableFuture<List<SearchResult>> getNextPage();
@Override void close(); }

So, in order to get all documents we should iterate over each page and check the type of document:

All together

Now, we can execute search using RxJava at presentation level:

Now we execute search through 2 types of documents: MovieDocument and PersonDocument:

The result of search

Conclusion

So, in this article I offer an example of working with the AppSearch library for full-text search for different documents. As already mentioned, similar functionality can be achieved using SQLite, but then you will have to join on several tables. In addition, AppSearch is tailored specifically for full-text search for a large number of heterogeneous documents, ideally if you have some media application with a large amount of content (Movies / Books / Games) and you need to search not only by description, but for example by name, a short description of the book, quotes, etc. The library is still at the alpha stage and most likely in the near future it will be possible to use Rx or Kotlin coroutines, but for now we have to work with LinstenableFuture.

As a workaround you can make wrappers through RxJava and work with it already. Don't forget to star source code on GitHub and clap if the article was useful.

--

--