Full Text Search in Room Tutorial: Getting Started
In this Android tutorial, you’ll learn how to implement Full Text Search in Room and use advanced FTS operations, such as ranking search results leading to a great search experience which provide relevant results, is fast, work offline and can handle large amounts of data. By Kshitij Chauhan.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
Full Text Search in Room Tutorial: Getting Started
25 mins
- Getting Started
- Full Text Search
- Virtual Tables
- FTS Tables
- Full Text Search and Room
- Creating FTS Tables
- Using FTS in a DAO
- Fixing the Return Type
- Maintaining the Full Text Search Index
- Triggering FTS Rebuilds
- Escaping FTS Queries
- Escaping double-quotes
- Ranking Search Results
- Model Class for matchinfo
- Using matchinfo in a DAO
- Sorting by Scores
- Using the Sorted Results
- Bonus: Tests
- Where to Go From Here?
Model Class for matchinfo
Navigate to the Launch.kt file in the db package and add the following code at the end:
data class LaunchWithMatchInfo(
@Embedded
val launch: Launch,
@ColumnInfo(name = "matchInfo")
val matchInfo: ByteArray
) {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as LaunchWithMatchInfo
if (launch != other.launch) return false
if (!matchInfo.contentEquals(other.matchInfo)) return false
return true
}
override fun hashCode(): Int {
var result = launch.hashCode()
result = 31 * result + matchInfo.contentHashCode()
return result
}
}
Here in this code, LaunchWithMatchInfo is a data class to hold the result of an FTS search returning a Launch object along with its matchinfo (represented as a ByteArray). Note that we need to need to override equals and hashcode methods due to the presence of an array property in the data class.
Next, you’ll add a DAO method to perform search using matchinfo.
Using matchinfo in a DAO
Navigate to the LaunchDao.kt file in the db package and add the following code to it:
@Query("""
SELECT *, matchinfo(launches_fts) as matchInfo
FROM launches
JOIN launches_fts ON launches.name = launches_fts.name
WHERE launches_fts MATCH :query
""")
suspend fun searchWithMatchInfo(query: String): List<LaunchWithMatchInfo>
This query returns a list of Launch objects, along with their matchinfo metadata. This list will be parsed in the SearchViewModel to order results according to their rank.
Sorting by Scores
Navigate to the SearchViewModel.kt file in the search package and add the following code to it (make sure add missing imports using the IDE):
fun searchWithScore(query: Editable?) {
// 1
viewModelScope.launch {
// 2
if (query.isNullOrBlank()) {
// 3
launchDao.all().let { _searchResults.postValue(it) }
} else {
// 4
val sanitizedQuery = sanitizeSearchQuery(query)
launchDao.searchWithMatchInfo(sanitizedQuery).let { results ->
// 5
results.sortedByDescending { result -> calculateScore(result.matchInfo) }
// 6
.map { result -> result.launch }
// 7
.let { _searchResults.postValue(it) }
}
}
}
}
Here in this code:
- Use the launch coroutine builder to start a coroutine
- Check if query is empty or null
- If the query is blank or null then query for all the values from the database and update the
_searchResultsLivedata value - If the query is not empty or null, then sanitize it and search the database using matchinfo.
- The
matchInfoarray of bytes is supplied to acalculateScorefunction, which parses it and returns a score value. A higher value indicates a better match, so the list is sorted in descending order according to the scores. - The sorted list of
LaunchWithMatchInfois then mapped to a simple list ofLaunchobjects, and used as the search results. - Update the
_searchResultsLivedata value
It is important to parse the matchinfo data and calculate the score on a background thread, because it is a potentially long-running operation for large datasets.
calculateScore function is outside this tutorial’s scope. It exists only to illustrate that you can use different ranking algorithms according to your needs. You may refer to the example in the original documentation to understand how it works.
Using the Sorted Results
Finally, navigate to the SearchFragment.kt file in the same package and update the setupSearchField method to use the newly added method in the ViewModel. Replace the existing implementation of setupSearchField method with below:
private fun setupSearchField() {
binding.searchBox.addTextChangedListener { query ->
viewModel.searchWithScore(query)
}
}
Build and run the app. You should have a search implementation that ranks results according to their scores!

Bonus: Tests
Writing tests is the best way to increase confidence in your code. Find My Launch sample apps ships with a couple of them to help verify your FTS implementation by testing the app’s ViewModels.
Navigate to the app module’s androidTest source set in Android Studio, where you’ll find two test files: DetailsViewModelTest and SearchViewModelTest.

Because both the tests are Instrumentation Tests, they require an emulator or a physical Android device to be connected to your computer. Once you have either of them up and running, select the ViewModel tests run configuration.

Run, and view the test results. If everything went well, all tests should pass!

To read more about testing Room databases and understand how these tests were written checkout Testing Android Architecture Components
Where to Go From Here?
You can download the final version of this project using the Download Materials button at the top or bottom of this tutorial.
Congratulations! You learned a lot in this tutorial and can now build an awesome search experience in your own Android apps.
If you are wondering what to learn next, you can checkout tutorial on Database Views with Room for Android, and Room DB: Advanced Data Persistence.
Feel free to share your feedback or findings, and please post your questions in the comments section below or in the forums. I hope that you enjoyed learning about FTS with Room!