Since I want to test foreign key features with SQLite, I am trying to make a simple app.
The app should display inventory information like this:
I made two tables on the SQLite database and added records directly by querying on Android Studio's Database Inspector.
items
tableprices
tableI tried to get each item's price by querying in the app, but Instance of 'Future<int>'
displayed. How can I display item prices correctly?
class SqliteForeignKeyScreen extends StatefulWidget {
const SqliteForeignKeyScreen({
Key? key,
}) : super(key: key);
@override
State<SqliteForeignKeyScreen> createState() => _SqliteForeignKeyScreenState();
}
class _SqliteForeignKeyScreenState extends State<SqliteForeignKeyScreen> {
Future<List<Item>>? _itemsList;
void _updateItemsList() {
setState(() {
_itemsList = DatabaseHelper.instance.getAllItemsList();
});
}
@override
void initState() {
super.initState();
_updateItemsList();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Items'),
),
body: FutureBuilder(
future: _itemsList,
builder: (BuildContext context, AsyncSnapshot snapshot) {
if (snapshot.hasError) {
Text('ERROR: ${snapshot.error}');
}
if (snapshot.hasData == false) {
return const CircularProgressIndicator();
}
if (snapshot.data.length == null || snapshot.data.length == 0) {
return const Text('no items');
}
return ListView.builder(
itemCount: snapshot.data.length,
itemBuilder: (_, index) {
return _buildItemCards(snapshot.data[index]);
});
},
),
);
}
_buildItemCards(Item item) {
var price = DatabaseHelper.instance.getItemPrice(item.id!);
print('item: ${item.name}, price: ${price}G');
return Card(
child: ListTile(
title: Text(item.name),
subtitle: Text('${price}G'),
),
);
}
}
database_helper.dart
class DatabaseHelper {
static final DatabaseHelper instance = DatabaseHelper._instance();
static Database? _db;
DatabaseHelper._instance();
Future<Database?> get db async {
_db ??= await _initDb();
return _db;
}
void _configureDb(Database db) async {
await db.execute('PRAGMA foreign_keys = ON;');
}
void _createDb(Database db, int version) async {
await db.execute('CREATE TABLE items('
'id INTEGER PRIMARY KEY AUTOINCREMENT,'
'name TEXT'
');');
await db.execute('CREATE TABLE prices('
'id INTEGER PRIMARY KEY AUTOINCREMENT,'
'item_id INTEGER,'
'price INTEGER,'
'FOREIGN KEY(item_id) REFERENCES items(id)'
');');
}
Future<Database> _initDb() async {
var databasePath = await getDatabasesPath();
String path = p.join(databasePath, 'inventory.db');
final inventoryDb = await openDatabase(path,
version: 1, onConfigure: _configureDb, onCreate: _createDb);
return inventoryDb;
}
Future<List<Map>> getAllItemsMapList() async {
Database? db = await this.db;
final List<Map<String, dynamic>> result = await db!.query('items');
return result;
}
Future<List<Item>> getAllItemsList() async {
final List<Map> itemsMapList = await getAllItemsMapList();
final List<Item> itemsList = [];
for (var itemMap in itemsMapList) {
itemsList.add(Item.fromMap(itemMap));
}
return itemsList;
}
Future<int> getItemPrice(int itemId) async {
final Database? db = await this.db;
final result =
await db!.query('prices', where: 'item_id = ?', whereArgs: [itemId]);
return result[0]['price'] as int;
}
}
item_model.dart
class Item {
int? id;
String name;
Item({
required this.name,
});
Item.withId({
this.id,
required this.name,
});
factory Item.fromMap(Map map) {
return Item.withId(
id: map['id'],
name: map['name'],
);
}
}
print('item: ${item.name}, price: ${price}G');
item: Apple, price: Instance of 'Future<int>'G
item: Banana, price: Instance of 'Future<int>'G
item: Chocolate, price: Instance of 'Future<int>'G
When I changed _buildItemCards
like this (added await
and async
):
_buildItemCards(Item item) async {
var price = await DatabaseHelper.instance.getItemPrice(item.id!);
print('item: ${item.name}, price: ${price}G');
return Card(
child: ListTile(
title: Text(item.name),
subtitle: Text('${price}G'),
),
);
}
The print('item: ${item.name}, price: ${price}G');
shows correctly:
item: Apple, price: 2G
item: Banana, price: 1G
item: Chocolate, price: 3G
However, the error type 'Future<dynamic>' is not a subtype of type 'Widget'
occurred on the screen.
The getItemPrice
is a Future function, so you should await
for its result, like this:
var price = await DatabaseHelper.instance.getItemPrice(item.id!);
And also the best way to use async function in build method is using FutureBuilder
, so change your _buildItemCards
to this:
FutureBuilder<int>(
future: DatabaseHelper.instance.getItemPrice(item.id!),
builder: (context, snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.waiting:
return Text('Loading....');
default:
if (snapshot.hasError) {
return Text('Error: ${snapshot.error}');
} else {
int price = snapshot.data!;
return Card(
child: ListTile(
title: Text(item.name),
subtitle: Text('${price}G'),
),
);
}
}
},
)