Guides / Building Search UI / Getting started

Getting Started with Flutter

This guide explains how to build a multi-platform search experience using Flutter and the community Dart API client. You’ll build a classic search interface with Flutter.

Prepare your project

Before you can use Algolia, you need an Algolia account. You can create a new one or use the following credentials (for a preloaded dataset of products appropriate for this guide):

  • Application ID: latency
  • Search API Key: 927c3fe76d4b52c5a2912973f35a3077
  • Index name: STAGING_native_ecom_demo_products

Create a new app project

Start by creating a new app. In a terminal, run:

1
flutter create algoliasearch

Add project dependencies

This tutorial uses the community Dart API client to integrate the Algolia libraries. Add the algolia dependency to pubspec.yaml of your project:

1
2
dependencies:
 algolia: ^1.1.1

In a terminal, run:

1
flutter pub get

Build a search interface with Flutter

  1. Open ./lib/main.dart, and add a SearchHit class that will represent the search hit. To keep this example simple, it will contain only a name and an image URL field. Declare a fromJson constructor method for conveniently creating SearchHit from a JSON string.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    
     class SearchHit {
       final String name;
       final String image;
    
       SearchHit(this.name, this.image);
    
       static SearchHit fromJson(Map<String, dynamic> json) {
         return SearchHit(json['name'], json['image_urls'][0]);
       }
     }
    
  2. In the main.dart file, look for the _MyHomePageState class. Remove its sample variables and method declarations, then add the Algolia object:

    1
    2
    
     final Algolia _algoliaClient = Algolia.init(
       applicationId: "latency", apiKey: "927c3fe76d4b52c5a2912973f35a3077");
    
  3. Add the _searchText (state of your query text) and the _hitsList (list of results) properties.

    1
    2
    
     String _searchText = "";
     List<SearchHit> _hitsList = [];
    
  4. Add the _textFieldController that controls and listens to the state of the TextField component you use as the search box.

    1
    
     TextEditingController _textFieldController = TextEditingController();
    
  5. Add a _getSearchResult function that calls the Algolia API and extracts the hits from the search response.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    
     Future<void> _getSearchResult(String query) async {
       AlgoliaQuery algoliaQuery = _algoliaClient.instance
           .index("STAGING_native_ecom_demo_products")
           .query(query);
       AlgoliaQuerySnapshot snapshot = await algoliaQuery.getObjects();
       final rawHits = snapshot.toMap()['hits'] as List;
       final hits = List<SearchHit>.from(rawHits.map((hit) => SearchHit.fromJson(hit)));
       setState(() {
         _hitsList = hits;
       });
     }
    
  6. Override the build method containing the user interface declaration. The interface is based on the Scaffold component. Add the AppBar with “Algolia & Flutter” as its title, and the Column component as its body:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    
     @override
     Widget build(BuildContext context) {
       return Scaffold(
         appBar: AppBar(
           title: Text('Algolia & Flutter'),
         ),
         body: Column(
           children: <Widget>[]
         )
       );
     }
    
  7. The Column’s body consists of two children: the search bar and the hits list. Start with adding a search box.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    
     @override
     Widget build(BuildContext context) {
       return Scaffold(
           appBar: AppBar(
             title: Text('Algolia & Flutter'),
           ),
           body: Column(children: <Widget>[
             Container(
                 height: 44,
                 child: TextField(
                   controller: _textFieldController,
                   decoration: InputDecoration(
                       border: InputBorder.none,
                       hintText: 'Enter a search term',
                       prefixIcon: Icon(Icons.search),
                       suffixIcon: _searchText.isNotEmpty
                           ? IconButton(
                               onPressed: () {
                                 setState(() {
                                   _textFieldController.clear();
                                 });
                               },
                               icon: Icon(Icons.clear),
                             )
                           : null),
                 )),
           ]));
     }
    
  8. Add the hits list widget as the second child of the Column:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    
     @override
     Widget build(BuildContext context) {
       return Scaffold(
           appBar: AppBar(
             title: Text('Algolia & Flutter'),
           ),
           body: Column(children: <Widget>[
             Container(
                 height: 44,
                 child: TextField(
                   controller: _textFieldController,
                   decoration: InputDecoration(
                       border: InputBorder.none,
                       hintText: 'Enter a search term',
                       prefixIcon: Icon(Icons.search),
                       suffixIcon: _searchText.isNotEmpty
                           ? IconButton(
                               onPressed: () {
                                 setState(() {
                                   _textFieldController.clear();
                                 });
                               },
                               icon: Icon(Icons.clear),
                             )
                           : null),
                 )),
             Expanded(
                 child: _hitsList.isEmpty
                     ? Center(child: Text('No results'))
                     : ListView.builder(
                         itemCount: _hitsList.length,
                         itemBuilder: (BuildContext context, int index) {
                           return Container(
                               color: Colors.white,
                               height: 80,
                               padding: EdgeInsets.all(8),
                               child: Row(children: <Widget>[
                                 Container(
                                     width: 50,
                                     child: Image.network(
                                         '${_hitsList[index].image}')),
                                 SizedBox(width: 20),
                                 Expanded(child: Text('${_hitsList[index].name}'))
                               ]));
                         }))
           ]));
     }
    
  9. Override the initState method. Add a TextFieldController listener to trigger a search request on each keystroke. You can also trigger an initial empty search from here.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    
     @override
     void initState() {
       super.initState();
       _textFieldController.addListener(() {
         if (_searchText != _textFieldController.text) {
           setState(() {
             _searchText = _textFieldController.text;
           });
           _getSearchResult(_searchText);
         }
       });
       _getSearchResult('');
     }
    
  10. Override the dispose method to remove the TextFieldController instance properly.

    1
    2
    3
    4
    5
    
     @override
     void dispose() {
       _textFieldController.dispose();
       super.dispose();
     }
    

The final result

The final version of your _MyHomePageState class should look as follows:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
class _MyHomePageState extends State<MyHomePage> {
  final Algolia _algoliaClient = Algolia.init(
      applicationId: "latency", apiKey: "927c3fe76d4b52c5a2912973f35a3077");

  String _searchText = "";
  List<SearchHit> _hitsList = [];
  TextEditingController _textFieldController = TextEditingController();

  Future<void> _getSearchResult(String query) async {
    AlgoliaQuery algoliaQuery = _algoliaClient.instance
        .index("STAGING_native_ecom_demo_products")
        .query(query);
    AlgoliaQuerySnapshot snapshot = await algoliaQuery.getObjects();
    final rawHits = snapshot.toMap()['hits'] as List;
    final hits =
        List<SearchHit>.from(rawHits.map((hit) => SearchHit.fromJson(hit)));
    setState(() {
      _hitsList = hits;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
          title: Text('Algolia & Flutter'),
        ),
        body: Column(children: <Widget>[
          Container(
              height: 44,
              child: TextField(
                controller: _textFieldController,
                decoration: InputDecoration(
                    border: InputBorder.none,
                    hintText: 'Enter a search term',
                    prefixIcon: Icon(Icons.search),
                    suffixIcon: _searchText.isNotEmpty
                        ? IconButton(
                            onPressed: () {
                              setState(() {
                                _textFieldController.clear();
                              });
                            },
                            icon: Icon(Icons.clear),
                          )
                        : null),
              )),
          Expanded(
              child: _hitsList.isEmpty
                  ? Center(child: Text('No results'))
                  : ListView.builder(
                      itemCount: _hitsList.length,
                      itemBuilder: (BuildContext context, int index) {
                        return Container(
                            color: Colors.white,
                            height: 80,
                            padding: EdgeInsets.all(8),
                            child: Row(children: <Widget>[
                              Container(
                                  width: 50,
                                  child: Image.network(
                                      '${_hitsList[index].image}')),
                              SizedBox(width: 20),
                              Expanded(child: Text('${_hitsList[index].name}'))
                            ]));
                      }))
        ]));
  }

  @override
  void initState() {
    super.initState();
    _textFieldController.addListener(() {
      if (_searchText != _textFieldController.text) {
        setState(() {
          _searchText = _textFieldController.text;
        });
        _getSearchResult(_searchText);
      }
    });
    _getSearchResult('');
  }

  @override
  void dispose() {
    _textFieldController.dispose();
    super.dispose();
  }
}

Save your changes in the main.dart file. Build and run your application by running flutter run in a terminal or your development tool. In the simulator, you should see the basic search interface built with Flutter.

Find the source code for this project in the Algolia Flutter playground repository on GitHub.

What’s next?

This tutorial gives an example of bridging native search with the community Dart API client and Flutter. Your actual application might be much more complex, so you might want to extend this example by adding more search parameters and API methods.

Did you find this page helpful?