I'm writing a kind of todo list in Flutter, with null safety enabled. I'm storing the list of todo on a sqlite db using sqflite.
The Item class is the following:
class Item {
int? _id;
String? _name;
String? _notes;
String? _expiryDate;
int? _category;
int? _quantity;
Item(
this._id,
this._name,
this._notes,
this._expiryDate,
this._category,
this._quantity,
);
// names for db table and columns
String tableItems = 'items';
String colId = 'id';
String colName = 'name';
String colNotes = 'notes';
String colExpiryDate = 'expiryDate';
String colCategory = 'category';
String colQuantity = 'quantity';
// Getters
int? get id => _id;
String? get name => _name;
String? get notes => _notes;
String? get expiryDate => _expiryDate;
int? get category => _category;
int? get quantity => _quantity;
// Imports the Item details from a map
Item.fromMap(Map map) {
this._id = map[colId];
this._name = map[colName];
this._notes = map[colNotes];
this._expiryDate = map[colExpiryDate];
this._category = map[colCategory];
this._quantity = map[colQuantity];
}
// Converts a Item into a Map
Map<String, dynamic> toMap() {
var map = Map<String, dynamic>();
map['id'] = _id;
map['name'] = _name;
map['notes'] = _notes;
map['expiryDate'] = _expiryDate;
map['category'] = _category;
map['quantity'] = _quantity;
return map;
}
// Implement toString to make it easier to see information about
// each dog when using the print statement.
@override
String toString() {
return 'Item{id: $_id, name: $_name, notes: $_notes, expiryDate: $_expiryDate, category: $_category, quantity: $_quantity}';
}
}
I have a class named items_controller which works as an intermediary between the Item class and the db_controller class. This is the items_controller class:
import 'package:get/get.dart';
import 'package:<redacted>/model/item.dart';
import 'db_controller.dart';
class ItemsController extends GetxController {
// names for db table and columns
String tableItems = 'items';
String colId = 'id';
String colName = 'name';
String colNotes = 'notes';
String colExpiryDate = 'expiryDate';
String colCategory = 'category';
String colQuantity = 'quantity';
//TODO
void loadItem() {}
// function to retrieve all the items from the db
Future<List<Item>> loadAllItems() async {
final dbController = DBController();
final List<Map<String, dynamic>> itemsMaps =
await dbController.query(tableItems);
return List.generate(itemsMaps.length, (i) {
return Item.fromMap(itemsMaps[i]);
});
}
// function to add an item to the db
Future<int> addItem(Item item) async {
final dbController = DBController();
// add a new Item to the table and get the id
int id = await dbController.insertElement(tableItems, item);
return id;
}
//TODO
void updateItem() {}
//TODO
void deleteItem(int index) {}
}
Given that, from the list_builder (which uses the ListView.builder method) I want to build a list using as index the length of the list of Items queried from the DB. But I have this error (as in title) [![screenshot][1]][1] [1]: https://i.stack.imgur.com/Z6VAh.png
I'm quite sure that the Future<List> that I'm getting when calling loadAllItems() should have a length property because it's a list, but I'm not sure, maybe because it's a Future List? Help please!
EDIT I've implemented the FutureBuilder, but I had no luck with the .length attribute: [![snapshot][2]][2] [2]: https://i.stack.imgur.com/QswA0.png
If I run anyway the code, I get the following errors:
Performing hot restart...
Syncing files to device AC2003...
lib/views/list_builder.dart:21:45: Error: The getter 'length' isn't defined for the class 'Object'.
- 'Object' is from 'dart:core'.
Try correcting the name to the name of an existing getter, or defining a getter or field named 'length'.
itemCount: snapshot.data!.length,
^^^^^^
lib/views/list_builder.dart:16:20: Error: A non-null value must be returned since the return type 'Widget' doesn't allow null.
- 'Widget' is from 'package:flutter/src/widgets/framework.dart' ('../../flutter/packages/flutter/lib/src/widgets/framework.dart').
builder: (context, snapshot) {
^
Restarted application in 671ms.
CodePudding user response:
Since the loadAllItems() call is a Future, you will need to wrap your ListBuilder in a FutureBuilder. Watch this video for some more information.
Basically, you need to await the results of the Future<List> so that it becomes a List.
CodePudding user response:
FutureBuilder
is what you required to load async data, and get list when the ConnectionState
is done
FutureBuilder(
future: controller.loadAllItems(),
builder: (BuildContext context, AsyncSnapshot<String> snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
int count = snapshot.data.length; // use this as itemCount in Listview
return ListView.Builder(); // write builder code here
}
}),
For read https://api.flutter.dev/flutter/widgets/FutureBuilder-class.html
CodePudding user response:
you guessed it right, because it's a future, so you have to use await
before the function,
you can use a FutureBuilder and pass the function to the future parameter, and access the list through the snapshot, something like that:
GetX<ItemController>(
builder: (controller) => FutureBuilder<List<Item>>(
future: controller.loadAllItems(),
builder: (context, snapshot) => snapshot.connectionState == ConnectionState.waiting
? CircularProgressIndicator()
: snapshot.hasData
? ListView.builder(
itemCount: snapshot.data.length,
itemBuilder: (context, index) => Container(
//data widget
),
)
: Container(
// empty widget
),
),
),
or you can the call the function when the page loads, and save the items in variable (in controller maybe, or the widget itself), and then use that variable to get the length
important note
make sure that future builder return a widget in every case, in your updated question, there is an error which indicates that the builder returns a null