{"id":15320,"date":"2021-09-18T11:11:15","date_gmt":"2021-09-18T11:11:15","guid":{"rendered":"https:\/\/flobiz.in\/blog\/?p=15320"},"modified":"2021-09-18T12:19:01","modified_gmt":"2021-09-18T12:19:01","slug":"full-text-search-with-firestore-on-android","status":"publish","type":"post","link":"http:\/\/73.248.200.35.bc.googleusercontent.com\/blog\/full-text-search-with-firestore-on-android\/","title":{"rendered":"Full Text Search with Firestore on Android"},"content":{"rendered":"\n<p id=\"1f1e\">A common use case in Android applications is to provide the user with the functionality to search through a list of records. While Firestore is a solid database to power read heavy applications, it does not offer an out of the box, full text search.<\/p>\n\n\n\n<p id=\"34fa\">The official Firestore&nbsp;<a href=\"https:\/\/firebase.google.com\/docs\/firestore\/solutions\/search\">documentation<\/a>&nbsp;regarding full text search suggests us to use a third-party search service such as Elastic Search or Algolia. However, choosing the right third-party search service comes with its own overheads such as pricing, maintaining an additional service layer etc.<\/p>\n\n\n\n<p id=\"6793\">So, is there a simpler way to implement full text search without a third-party service?<\/p>\n\n\n\n<p id=\"c109\">Enter, Hackerman.<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img decoding=\"async\" src=\"https:\/\/miro.medium.com\/max\/1400\/1*dRudObMfI_t9SNzYcpQA9A.jpeg\" alt=\"\"\/><\/figure>\n\n\n\n<p id=\"8b99\">If you haven\u2019t already setup Firestore with your project, you can follow the official&nbsp;<a href=\"https:\/\/firebase.google.com\/docs\/firestore\/quickstart\">documentation<\/a>&nbsp;to get started.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"b77c\">The Use Case<\/h3>\n\n\n\n<p id=\"e6c0\">Let\u2019s say that we\u2019re building an Android application that lists a set of products available at a grocery shop. To keep things simple, let\u2019s assume that each product has just two fields \u2014 its name and its price.<\/p>\n\n\n\n<p id=\"9f4e\">Here\u2019s how our schema looks like in Firestore, with a root level collection called&nbsp;<em>products<\/em>&nbsp;and each document inside it corresponds to a single grocery item.<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img decoding=\"async\" src=\"https:\/\/miro.medium.com\/max\/1400\/1*mEdL7WtgT1rx1Qra9ev8-w.png\" alt=\"\"\/><\/figure>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"f1cc\">Solution Overview<\/h3>\n\n\n\n<p id=\"5e8e\">Our solution comprises of the following steps:<\/p>\n\n\n\n<ol class=\"wp-block-list\"><li>Setting up our Firestore collection<\/li><li>Generating&nbsp;<code>keywords<\/code>&nbsp;for each product name<\/li><li>And finally, use&nbsp;<code>whereArrayContains<\/code><strong>&nbsp;<\/strong>to power our search functionality<\/li><\/ol>\n\n\n\n<p id=\"f1cf\"><strong>Before we continue, it\u2019s highly important to understand the limitations of this approach.<\/strong><\/p>\n\n\n\n<ol class=\"wp-block-list\"><li>It is not possible to query a Firestore database with more than one&nbsp;<code>whereArrayContains<\/code><\/li><li>Firestore indices have&nbsp;<a href=\"https:\/\/firebase.google.com\/docs\/firestore\/query-data\/index-overview#indexing_limits\">limits<\/a>, and as our dataset expands, it is possible that we\u2019ll hit this upper limit<\/li><\/ol>\n\n\n\n<p id=\"46a1\">Understanding these limitations is crucial as these could become major blockers as your application scales.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"3030\">Generating Keywords<\/h3>\n\n\n\n<p id=\"9a78\">The keywords array will power our search functionality. When we query Firestore in the next steps, we will check if our search query is contained in the keywords array for any document. Therefore, we want to ensure that our keywords array contains an exhaustive set of entries that cover all possible combinations of the product\u2019s name. There are&nbsp;<a href=\"https:\/\/www.geeksforgeeks.org\/how-to-generate-all-combinations-of-a-string-in-javascript\/\">multiple ways<\/a>&nbsp;to generate these keywords.<\/p>\n\n\n\n<p id=\"f34b\">Following the first approach, here\u2019s how it looks like when translated to Kotlin:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\"><strong>fun generateKeywords(name: String): List&lt;String&gt; {<\/strong><br>    val keywords = mutableListOf&lt;String&gt;()<br>    for (i in 0 until name.length) {<br>        for (j in (i+1)..name.length) {<br>            keywords.add(name.slice(i until j))<br>        }<br>    }<br>   return keywords<br>}<\/pre>\n\n\n\n<p id=\"1a2b\">Here are some keywords generated for \u2014&nbsp;<em>Premium Filter Coffee 250gm<\/em><\/p>\n\n\n\n<figure class=\"wp-block-image\"><img decoding=\"async\" src=\"https:\/\/miro.medium.com\/max\/875\/1*iCreA3vtDADB5jZPoyTs5Q.png\" alt=\"\"\/><\/figure>\n\n\n\n<p id=\"4fa1\">Now that we have our data ready, we can add it to Firestore. Each document now has three fields \u2014 name, price and keywords.<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\"><strong>data class Product (<\/strong><br>    val name: String,<br>    val price: Double,<br>    val keywords: List&lt;String&gt;<br><strong>)<\/strong><\/pre>\n\n\n\n<p id=\"8d68\">and can be added to our database with the following piece of code:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">Firebase<br>.firestore<br>.collection(\"products\")<br>.add(Product(name, price, <strong>generateKeywords(name)<\/strong>)<\/pre>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"d7ff\"><strong>Querying Firestore<\/strong><\/h3>\n\n\n\n<p id=\"0ef1\">Let\u2019s write a method that returns our database query:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\"><strong>fun getProductsByName(searchQuery: String) =<\/strong> Firebase.firestore<em><br>    <\/em>.collection(\"products\")<br>    .whereArrayContains(\"keywords\", searchQuery.<em>trim<\/em>())<br>    .limit(50)<\/pre>\n\n\n\n<p id=\"8b90\">Limits are optional, but recommended, as they throttle our search results and ensure that we don\u2019t accidentally overuse our Firestore free limits \/ budgets.<\/p>\n\n\n\n<p id=\"0fee\">We can now integrate this method directly with our UI or interface it with a view model. I prefer using the latter approach.<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\"><strong>private val observer =<\/strong> MutableLiveData&lt;List&lt;Product&gt;&gt;()<br><strong>fun liveData():<\/strong> LiveData&lt;List&lt;Product&gt;&gt; = observer<br><strong>fun getProductsByName(searchQuery: String) =<\/strong><br>    mRepository<br>    .getProductsByName(searchQuery)<br>    .get()<br>    .addOnCompleteListener <strong>{ <\/strong>task <strong>-&gt;<br>        <\/strong>if (task.<em>isSuccessful<\/em>)<br>      observer.postValue(task.<em>result<\/em>.toObjects(Product::class.<em>java<\/em>))<br>    <strong>}<\/strong><\/pre>\n\n\n\n<p id=\"6abd\">And that\u2019s it!<\/p>\n\n\n\n<p id=\"3a5c\">That\u2019s how we can power full text search in Firestore with its native, out of the box capabilities. However, I would like to reiterate its limitations so you can make the best decision for your application considering the use case and your roadmap.<\/p>\n\n\n\n<p id=\"c8b2\">I would also suggest for you to&nbsp;<a href=\"https:\/\/css-tricks.com\/debouncing-throttling-explained-examples\/\">debounce<\/a>&nbsp;your search listeners, to optimise the number of queries we end up making to the Firestore database.<\/p>\n\n\n\n<p><a href=\"https:\/\/sarthak1-garg.medium.com\/?source=post_page-----622af6ca5410--------------------------------\"><\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Did you know that there is a simpler way to implement full text search without a third-party service. <\/p>\n","protected":false},"author":7,"featured_media":15330,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_monsterinsights_skip_tracking":false,"_monsterinsights_sitenote_active":false,"_monsterinsights_sitenote_note":"","_monsterinsights_sitenote_category":0,"footnotes":""},"categories":[149],"tags":[],"class_list":["post-15320","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-engineering-product"],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v16.1.1 - https:\/\/yoast.com\/wordpress\/plugins\/seo\/ -->\n<title>Full Text Search with Firestore on Android - Flobiz<\/title>\n<meta name=\"description\" content=\"Here&#039;s how you can do full text search with firestore on Android.\" \/>\n<link rel=\"canonical\" href=\"http:\/\/35.200.248.73\/blog\/full-text-search-with-firestore-on-android\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Full Text Search with Firestore on Android - Flobiz\" \/>\n<meta property=\"og:description\" content=\"Here&#039;s how you can do full text search with firestore on Android.\" \/>\n<meta property=\"og:url\" content=\"http:\/\/35.200.248.73\/blog\/full-text-search-with-firestore-on-android\/\" \/>\n<meta property=\"og:site_name\" content=\"FloBiz\" \/>\n<meta property=\"article:published_time\" content=\"2021-09-18T11:11:15+00:00\" \/>\n<meta property=\"article:modified_time\" content=\"2021-09-18T12:19:01+00:00\" \/>\n<meta property=\"og:image\" content=\"http:\/\/73.248.200.35.bc.googleusercontent.com\/blog\/wp-content\/uploads\/2021\/09\/Untitled-design-3.png\" \/>\n\t<meta property=\"og:image:width\" content=\"1200\" \/>\n\t<meta property=\"og:image:height\" content=\"675\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:label1\" content=\"Est. reading time\">\n\t<meta name=\"twitter:data1\" content=\"4 minutes\">\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\/\/schema.org\",\"@graph\":[{\"@type\":\"WebSite\",\"@id\":\"https:\/\/35.200.248.73\/blog\/#website\",\"url\":\"https:\/\/35.200.248.73\/blog\/\",\"name\":\"FloBiz\",\"description\":\"\",\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":\"https:\/\/35.200.248.73\/blog\/?s={search_term_string}\",\"query-input\":\"required name=search_term_string\"}],\"inLanguage\":\"en-US\"},{\"@type\":\"ImageObject\",\"@id\":\"http:\/\/35.200.248.73\/blog\/full-text-search-with-firestore-on-android\/#primaryimage\",\"inLanguage\":\"en-US\",\"url\":\"http:\/\/73.248.200.35.bc.googleusercontent.com\/blog\/wp-content\/uploads\/2021\/09\/Untitled-design-3.png\",\"contentUrl\":\"http:\/\/73.248.200.35.bc.googleusercontent.com\/blog\/wp-content\/uploads\/2021\/09\/Untitled-design-3.png\",\"width\":1200,\"height\":675,\"caption\":\"Full Text Search with Firestore on Android\"},{\"@type\":\"WebPage\",\"@id\":\"http:\/\/35.200.248.73\/blog\/full-text-search-with-firestore-on-android\/#webpage\",\"url\":\"http:\/\/35.200.248.73\/blog\/full-text-search-with-firestore-on-android\/\",\"name\":\"Full Text Search with Firestore on Android - Flobiz\",\"isPartOf\":{\"@id\":\"https:\/\/35.200.248.73\/blog\/#website\"},\"primaryImageOfPage\":{\"@id\":\"http:\/\/35.200.248.73\/blog\/full-text-search-with-firestore-on-android\/#primaryimage\"},\"datePublished\":\"2021-09-18T11:11:15+00:00\",\"dateModified\":\"2021-09-18T12:19:01+00:00\",\"author\":{\"@id\":\"https:\/\/35.200.248.73\/blog\/#\/schema\/person\/9a658391bc1bd4a4dcb0c3eac063cae6\"},\"description\":\"Here's how you can do full text search with firestore on Android.\",\"breadcrumb\":{\"@id\":\"http:\/\/35.200.248.73\/blog\/full-text-search-with-firestore-on-android\/#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"http:\/\/35.200.248.73\/blog\/full-text-search-with-firestore-on-android\/\"]}]},{\"@type\":\"BreadcrumbList\",\"@id\":\"http:\/\/35.200.248.73\/blog\/full-text-search-with-firestore-on-android\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"item\":{\"@type\":\"WebPage\",\"@id\":\"https:\/\/35.200.248.73\/blog\/\",\"url\":\"https:\/\/35.200.248.73\/blog\/\",\"name\":\"Home\"}},{\"@type\":\"ListItem\",\"position\":2,\"item\":{\"@type\":\"WebPage\",\"@id\":\"http:\/\/35.200.248.73\/blog\/full-text-search-with-firestore-on-android\/\",\"url\":\"http:\/\/35.200.248.73\/blog\/full-text-search-with-firestore-on-android\/\",\"name\":\"Full Text Search with Firestore on Android\"}}]},{\"@type\":\"Person\",\"@id\":\"https:\/\/35.200.248.73\/blog\/#\/schema\/person\/9a658391bc1bd4a4dcb0c3eac063cae6\",\"name\":\"Sarthak Garg\",\"image\":{\"@type\":\"ImageObject\",\"@id\":\"https:\/\/35.200.248.73\/blog\/#personlogo\",\"inLanguage\":\"en-US\",\"url\":\"https:\/\/secure.gravatar.com\/avatar\/bee27a54b0195d4d219f0aeafd1062ca33346ad05c60e348ee63291170bdae4a?s=96&d=mm&r=g\",\"contentUrl\":\"https:\/\/secure.gravatar.com\/avatar\/bee27a54b0195d4d219f0aeafd1062ca33346ad05c60e348ee63291170bdae4a?s=96&d=mm&r=g\",\"caption\":\"Sarthak Garg\"}}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","_links":{"self":[{"href":"http:\/\/73.248.200.35.bc.googleusercontent.com\/blog\/wp-json\/wp\/v2\/posts\/15320","targetHints":{"allow":["GET"]}}],"collection":[{"href":"http:\/\/73.248.200.35.bc.googleusercontent.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"http:\/\/73.248.200.35.bc.googleusercontent.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"http:\/\/73.248.200.35.bc.googleusercontent.com\/blog\/wp-json\/wp\/v2\/users\/7"}],"replies":[{"embeddable":true,"href":"http:\/\/73.248.200.35.bc.googleusercontent.com\/blog\/wp-json\/wp\/v2\/comments?post=15320"}],"version-history":[{"count":3,"href":"http:\/\/73.248.200.35.bc.googleusercontent.com\/blog\/wp-json\/wp\/v2\/posts\/15320\/revisions"}],"predecessor-version":[{"id":15332,"href":"http:\/\/73.248.200.35.bc.googleusercontent.com\/blog\/wp-json\/wp\/v2\/posts\/15320\/revisions\/15332"}],"wp:featuredmedia":[{"embeddable":true,"href":"http:\/\/73.248.200.35.bc.googleusercontent.com\/blog\/wp-json\/wp\/v2\/media\/15330"}],"wp:attachment":[{"href":"http:\/\/73.248.200.35.bc.googleusercontent.com\/blog\/wp-json\/wp\/v2\/media?parent=15320"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"http:\/\/73.248.200.35.bc.googleusercontent.com\/blog\/wp-json\/wp\/v2\/categories?post=15320"},{"taxonomy":"post_tag","embeddable":true,"href":"http:\/\/73.248.200.35.bc.googleusercontent.com\/blog\/wp-json\/wp\/v2\/tags?post=15320"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}