Search code examples
flutterdartsqfliteflutter-futurebuilder

Flutter problem with access to Sqflite data via FutureBuilder. ConnectionState.done, null


Trying to change access to database from:

simply initializing access to data initState and then showing results in ListView.builder

to

ListView.builder inside the FutureBuilder.

However I lost my way in this FutureBuilder and my list is empty. I guess my problem is inside updateAndGetList(). In console I am getting this:

flutter: AsyncSnapshot<List<Word>>(ConnectionState.done, null, , #0      _RandomWordsState.updateAndGetList.<anonymous closure> (package:myapplesson/main.dart:115:7)

Can somebody help or nudge me in a right direction?🥲

Here my main.dart

DatabaseHelper dbHelper = DatabaseHelper.instance;
final _suggestions = <Word>[];

  @override
  void initState() {
    super.initState();
    // initial load
    _listFuture = updateAndGetList();
  }

  late Future<List<Word>> _listFuture;

  void refreshList() {
    // reload
    setState(() {
      _listFuture = updateAndGetList();
    });
  }

//I GUESS I AM DOING SOMETHING WRONG HERE!?👇👇👇

  Future<List<Word>> updateAndGetList() async {
    await DatabaseHelper.instance.database;
    // return the list here
    return dbHelper.getAllWords().then((rows) {
      setState(() {
        rows?.forEach((row) { _suggestions.add(Word.map(row));});
      });
      throw '';
    });
  }

  Widget _buildSuggestions() {
    return FutureBuilder<List<Word>>(
      future: _listFuture,
      builder: (BuildContext context, AsyncSnapshot<List<Word>> snapshot) {
        if (snapshot.hasData) {
          return ListView.builder(
            itemCount: _suggestions.length,
            itemBuilder: (context, i) {
              if (i.isOdd) return const Divider();
              final index = i ~/ 2;
              if (index >= _suggestions.length) {
                _suggestions.addAll(_suggestions.take(10));
              }
              return _buildRow(_suggestions[index]);
            });
        }else if (snapshot.hasError) {
          return Text("Oops!");
        }
        return Center(child: CircularProgressIndicator());
      },
    );
  }

Widget _buildRow(Word pair) {
  return ListTile(
    title: Text(
      pair.word1
    ),
    subtitle: Text(
        pair.meaning
    ),
  );
}

Before the changes were done everything worked this way:

//perfectly worked😀

@override
void initState() {
  super.initState();
  dbHelper.getAllWords().then((rows) {
    setState(() {
      rows?.forEach((row) { _suggestions.add(Word.map(row));});
    });
  });
}

Widget _buildSuggestions() {
  return ListView.builder(
      itemCount: _suggestions.length,
      itemBuilder: (context, i) {
        if (i.isOdd) return const Divider();
        final index = i ~/ 2;
        if (index >= _suggestions.length) {
          _suggestions.addAll(_suggestions.take(10));
        }
        return _buildRow(_suggestions[index]);
      });
}
Widget _buildRow(Word pair) {
  return ListTile(
    title: Text(
      pair.word1
    ),
    subtitle: Text(
        pair.meaning
    ),
  );
}

This is my Database_helper.dart

...
Future<List<Map<String, dynamic>>?> getAllWords() async {
    Database? db = await instance.database;
    var result = await db?.query(table);
    return result?.toList();
  }
...

Solution

  • In your updateAndGetList function, you are expected to return a Future<List<Word>>. Instead, you don't return anything (but set the state instead). Consider adapting it to look a bit like the following:

      Future<List<Word>> updateAndGetList() async {
        await DatabaseHelper.instance.database;
        // return the list here
        return dbHelper.getAllWords().then((rows) {
          // build the list of words and then return it!
          List<Word> res = [];
          for (var row in rows) {
            res.add(Word.map(row));;      
          }
          return res;
        });
      }
    

    You can now access this list of words from your snapshot in your future builder

    return FutureBuilder<List<Word>>(
          future: _listFuture,
          builder: (BuildContext context, AsyncSnapshot<List<Word>> snapshot) {
            if (snapshot.hasData) {
              
              // the word list as retrieved from your future
              List<Word> wordList = snapshot.data;
              return ListView.builder(
                itemCount: wordList.length,
                itemBuilder: (context, i) {
                  if (i.isOdd) return const Divider();
                  final index = i ~/ 2;
                  if (index >= wordList.length) {
                    wordList.addAll(wordList.take(10));
                  }
                  // use the word listto build your rows
                  return _buildRow(wordList[index]);
                });
            }else if (snapshot.hasError) {
              return Text("Oops!");
            }
            return Center(child: CircularProgressIndicator());
          },
        );
    

    No need to keep a separate variable for your suggestions.

    Moreover, you won't really need your _listFuture variable, unless you require it somewhere else. In your FutureBuilder you can simply use

    return FutureBuilder<List<Word>>(
      future: updateAndGetList(),
      builder: ...
    );