Search code examples
flutterfirebasegoogle-cloud-firestorealgolia

I want to use flutter to display algolia results in a listview, but nothing is displayed


I'm new to flutter and I'm building a program that puts text in Textfield and searches for it in algolia.

It works fine up to the point where I enter text and algolia processes it, but it doesn't return any results and nothing is displayed on the screen.

Is it because I am using Stream Builder? (Would Futurebuilder be better?) I'm not sure, so I would appreciate it if someone could help me.

import 'dart:async';
import 'package:algolia/algolia.dart';
import 'package:flutter/material.dart';

import '../../main.dart';

class Application {
  static final Algolia algolia = Algolia.init(
    applicationId: '78CZVABC2W',
    apiKey: 'c2377e7faad9a408d5867b849f25fae4',
  );
}



class Search extends StatefulWidget {
  @override
  _SearchState createState() => _SearchState();
}

class _SearchState extends State<Search> {
  StreamController<List<AlgoliaObjectSnapshot>> searchController = StreamController();

  Future searchFireStore(word) async {
    Algolia algolia = Application.algolia;
    AlgoliaQuery query = algolia.instance.index("rigaku_index").query(word);
    AlgoliaQuerySnapshot snap = await query.getObjects();
    List<AlgoliaObjectSnapshot> hits = snap.hits;
    searchController.add(hits);
    print(hits.length);

  }

  final _algilia = Application.algolia;

  @override
  void dispose() {
    searchController.close();
    super.dispose();
  }


  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: SearchItems(),
        backgroundColor: Colors.white,
      ),
      body: Column(
        children: <Widget>[
          Expanded(
            child: StreamBuilder(
              stream: searchController.stream,
              builder: (context,AsyncSnapshot snapshot) {
                if (snapshot.hasData) {
                  return ListView.builder(
                    itemCount: snapshot.data.length,
                    itemBuilder: (context, index) {
                      return Text((snapshot.data[index].data['zyugyoumei']));
                    },
                  );
                } else {
                  return Center(
                    child: Text("No Data"),
                  );
                }

              },

            ),
          )
        ],
      ),
    );
  }
}

class SearchItems extends StatefulWidget {
  @override
  _SearchItemsState createState() => _SearchItemsState();
}

class _SearchItemsState extends State<SearchItems> {
  String searchWord = "";

  @override
  Widget build(BuildContext context) {
    return TextField(
        decoration: const InputDecoration(
          hintText: '検索',
        ),
        controller: TextEditingController(text: searchWord),
        onChanged: (String text) {
          setState(() {
            searchWord = text;
          });
        },
        onSubmitted: (searchWord) =>_SearchState().searchFireStore(searchWord));  }
}


There are no error messages, but the snapshot in the StreamBuilder is null. The [hits] in Future is displayed correctly.


Solution

  • There are the following problems solving those should get you your desired result:

    1. If you Observe your searchController is not typed properly as per your data structure type will emit something like
    StreamController<List<AlgoliaObjectSnapshot>> searchController = StreamController();
    

    In this way we know what we expect to return after each hit.

    1. You are not setting searchWord properly is should be set with using setState like following:
    onChanged: (String text) {
      setState(() {
        searchWord = text;
      });
    },
    
    1. You are calling searchFireStore incorrectly instead of _SearchState().searchFireStore(searchWord) you should create instance variable _SearchState _searchState; and calling it like _searchState.searchFireStore(searchWord); where _searchState is an instance variable of type _SearchState that we have just created.
    2. And this last one is just for safety to dispose of the searchController properly as follows:
    @override
    void dispose() {
      searchController.close();
      super.dispose();
    }
    
    

    And for your using StreamBuilder vs FutureBuilder debate it depends on your use case like StreamBuilder is more appropriate for cases where you expect the data to be updated frequently, while FutureBuilder is more appropriate for cases where the data is fetched once and doesn't change often.

    In your case, since you're using a stream to receive search results, so in this case StreamBuilder seems correct choice.

    For more information go through StreamBuilder vs FutureBuilder and Algolia ListView Search

    EDIT : I have managed to recreate all the algolia with flutter as follows:

    import 'package:flutter/material.dart';
    import 'package:firebase_core/firebase_core.dart';
    import 'firebase_options.dart';
    import 'package:algolia/algolia.dart';
    import 'dart:async';
    
    void main() async {
      WidgetsFlutterBinding.ensureInitialized();
      await Firebase.initializeApp(
        options: DefaultFirebaseOptions.currentPlatform,
      );
      runApp(const MyApp());
    }
    
    class MyApp extends StatelessWidget {
      const MyApp({Key? key}) : super(key: key);
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          title: 'Flutter Algolia Search',
          theme: ThemeData(
            primarySwatch: Colors.blue,
          ),
          home: Search(),
        );
      }
    }
    
    class Search extends StatefulWidget {
      @override
      _SearchState createState() => _SearchState();
    }
    
    class _SearchState extends State<Search> {
      StreamController<List<AlgoliaObjectSnapshot>> searchController =
          StreamController();
      List<AlgoliaObjectSnapshot> _results = [];
    
      final Algolia algolia = const Algolia.init(
        applicationId: '<app_id>',
        apiKey: '<api_key>',
      );
    
      Future<List<AlgoliaObjectSnapshot>> searchAlgolia(String query) async {
        AlgoliaQuery algoliaQuery = algolia.instance.index('demo').query(query);
        AlgoliaQuerySnapshot snap = await algoliaQuery.getObjects();
        return snap.hits;
      }
    
      void _performSearch(String query) async {
        List<AlgoliaObjectSnapshot> results = await searchAlgolia(query);
        setState(() {
          _results = results;
        });
      }
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(
            title: const Text('Algolia Search'),
          ),
          body: Column(
            children: <Widget>[
              TextField(
                onChanged: (query) => _performSearch(query),
                decoration: const InputDecoration(
                  hintText: 'Search...',
                  prefixIcon: Icon(Icons.search),
                ),
              ),
              Expanded(
                child: ListView.builder(
                  itemCount: _results.length,
                  itemBuilder: (context, index) {
                    AlgoliaObjectSnapshot result = _results[index];
                    return ListTile(
                      title: Text(result.data['firstname']),
                      subtitle: Text(result.data['lastname']),
                    );
                  },
                ),
              ),
            ],
          ),
        );
      }
    }
    

    So the only thing need to be edited here is app_id api_key and off course the index name and it's values.