DEV Community

Samarth Agarwal
Samarth Agarwal

Posted on

Full-Text Search in Flutter with Algolia

# This article was originally posted on my Medium publication. Here is a link to the original.

Searching is one of the most important features of any mobile application that deals with data. It is almost inevitable to not have a reliable and consistent search in a mobile application these days. If your app deals with data that it retrieves from an API or a web service, you can have the backend developers develop the search mechanism for you and you can then consume the search results via the API, but, if you are using something like Cloud Firestore to store your data, you are in a puddle.

You can perform basic searches in Cloud Firestore where you need to match some fields with some values and order the results in a certain order as long as you are not dealing with text. Basically, it is not even searching. You can only look for exact value matches in the documents fields. For example, if a document has a field called name with the value John Doe, your search query should contain the exact field name and its value to be able to retrieve this document. Even if you use John or Doe as your search string, it won’t work because for text, you always check for equality in Firestore.

Recently, I had to add the search functionality to an app I have been working on and I had to write the search logic in such a way that it should consider multiple fields within a document on Cloud Firestore. For example, If I have the following documents inside my Posts collection.

post_id: 1234
post_title: Web Development for Beginners
post_text: This is a wonderful book for those who want to learn web development from scratch.

post_id: 1235
post_title: Software Development for Beginners
post_text: ...

post_id: 1236
post_title: Programming for Beginners
post_text: ...

In the above dataset, we can only perform a search within Firestore if the search query is exactly the text that we have stored in the field in the document. Firestore will not return any results for the following query.

Firestore.collection("Posts").where("post_title", "==", "Software").get()

And that’s obvious. The search term software does not match exactly within any document’s post_title field. And the problem maximizes when you are supposed to search across fields. Moreover, you cannot expect your users to be able to type the title of the book exactly the same way you have it in the database.

Enter Algolia.
Algolia

I have been an Algolia user and it is a life-saver if you are using Firestore and 3 months into development you realize you are in a puddle.

I used Algolia around 2 years ago for the first time for a project and I decided to use it again. This was a piece of cake for Algolia considering what it can do with JSON data. So the first step in integrating Algolia in your app is to decide what data you want to perform your searches on. Narrow it down as much as you can and then add that data to Algolia.

Algolia has SDKs for both clients and servers in many popular programming languages but not Dart! But we will get around that. For now, I will be writing a small NodeJS script that will read all the documents from the Posts collection and add them to an index on Algolia. An Algolia index is like a collection of related data. An index has JSON documents within which it searches.
I will be using the Firebase’s Admin SDK to sync Firestore and Algolia. I will be using the algoliasearch NodeJS package to use the Algolia SDK. I will be skipping the details of this.

const algolia = algoliasearch("APP_ID", "API_ADMIN_KEY");
const index = algolia.initIndex("Posts"); // You can choose any name
let records = [];
let querySnapshot = await admin.firestore().collection("Posts").get();

for (let i in querySnapshot.docs) {
    let obj = querySnapshot.docs[i].data();
    obj.objectID = querySnapshot.docs[i].id;
    records.push(obj);
}

await index.saveObjects(records);
console.log("Pushed everything to Algolia"); 

You can have a look at this video on my YouTube channel to see how I uploaded the data from a Firestore collection to an Algolia index.

The above code snippet just reads all the documents from the Posts collection and inserts them one by one in the results array. Each object in the array has a property called objectID that Algolia needs to index your data. Once the script finishes, you will have all the documents in an Algolia index called Posts.

Index created on Algolia

Go to the configuration section, select the Searchable Attributes option on the left, and add the post_title and post_text both to the list of searchable attributes. This will allow Algolia to search within both these fields.

Added post_text and post_title to searchable attributes

Now that we have the data on Algolia and we have configured how we want to perform our searches, let’s see how we can implement search in the Flutter App.

Unfortunately, there is no Official SDK for Dart/Flutter for Algolia but you can use the amazing package dart_algolia which is a wrapper around the REST API or you are feeling wild, you can go ahead with the REST API yourself. Your options are open.

GitHub logo knoxpo / dart_algolia

[Unofficial] Algolia is a pure dart SDK, wrapped around Algolia REST API for easy implementation for your Flutter or Dart projects.

I will be creating a brand new app in Flutter and quickly install the dart_algolia package by adding it as a dependency in the pubspec.yaml file. Save the file and run flutter packages get command to install the package.

Added algolia as a dependency in pubspec.yaml

Next, in the main.dart file, we will create a minimal UI with a text field and a button to perform the search. Here is the complete build() method.

Widget build(BuildContext context) {
  return Scaffold(
    appBar: AppBar(
      title: Text("Algolia Search"),
    ),
    body: Container(
      padding: const EdgeInsets.all(16.0),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: <Widget>[
          Text("Search"),
          TextField(
            decoration: InputDecoration(hintText: "Search query here..."),
          ),
          Row(
            mainAxisAlignment: MainAxisAlignment.end,
            children: <Widget>[
              FlatButton(
                color: Colors.blue,
                child: Text(
                  "Search",
                  style: TextStyle(color: Colors.white),
                ),
                onPressed: () {},
              ),
            ],
          )
        ],
      ),
    ), // This trailing comma makes auto-formatting nicer for build methods.
  );
}

And here is how it looks like.

Preview of the UI

Next, let’s quickly add a TextEditingController so that we can access the value of the TextField.

TextEditingController _searchText = TextEditingController(text: "");

And then assign the _searchText to the controller property of the TextField.

TextField(
  controller: _searchText,
  decoration: InputDecoration(hintText: "Search query here..."),
),

Finally, it is time to write the code for the onPressed event on the FlatButton. Let’s create a method called search and write all the search logic inside of it.

FlatButton(
  color: Colors.blue,
  child: Text(
    "Search",
    style: TextStyle(color: Colors.white),
  ),
  onPressed: _search,
),

We will now add a few variables at the top inside the class.

List<AlgoliaObjectSnapshot> _results = [];
bool _searching = false;

The _results array will hold the data returned by Algolia and we will use this to generate a ListView. The _searching boolean will just be used to indicate if searching has completed or not.

_search() async {
  setState(() {
    _searching = true;
  });

  Algolia algolia = Algolia.init(
    applicationId: 'APP_ID',
    apiKey: 'SEARCH_API_KEY',
  );

  AlgoliaQuery query = algolia.instance.index('Posts');
  query = query.search(_searchText.text);

  _results = (await query.getObjects()).hits;

  setState(() {
    _searching = false;
  });
}

Finally, let’s create the ListView using ListView.builder widget constructor. We will put it as the last element inside the Column and wrap it inside an Expanded widget so that it occupies all the available vertical space.

Expanded(
  child: _searching == true
      ? Center(
          child: Text("Searching, please wait..."),
        )
      : _results.length == 0
          ? Center(
              child: Text("No results found."),
            )
          : ListView.builder(
              itemCount: _results.length,
              itemBuilder: (BuildContext ctx, int index) {
                AlgoliaObjectSnapshot snap = _results[index];

                return ListTile(
                  leading: CircleAvatar(
                    child: Text(
                      (index + 1).toString(),
                    ),
                  ),
                  title: Text(snap.data["post_title"]),
                  subtitle: Text(snap.data["post_text"]),
                );
              },
            ),
),

All done. The code can be a little bit cleaner but let’s ignore it for now. Let’s test this out.

Final App Demo

That is just the beginning. Algolia offers tremendous power and allows you to perform facetted searches, geolocation-based searches, ranking and sorting, and a lot more. I am still experimenting and you should give it a shot. Also, feel free to share your experiences.

The code for the demo above is available on GitHub.

Here is a complete video on YouTube where I have implemented Full-text search using Algolia in a Flutter app.

You can also subscribe to my YouTube channel for more fun content.

Keep coding, cheers.

Top comments (0)