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
table
prices
table
I 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?
Main code
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'],
);
}
}
Result of 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.
CodePudding user response:
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'),
),
);
}
}
},
)