Flutter - 编写高级应用程序
在本章中,我们将学习如何编写一个完整的移动应用程序 expense_calculator。该应用程序的目的是存储我们的支出信息。应用程序的完整功能如下:
支出列表。
输入新支出的表单。
编辑/删除现有支出的选项。
任意时刻的总支出。
我们将使用以下 Flutter 框架的高级特性来编程 expense_calculator 应用程序。
高级使用 ListView 显示支出列表。
表单编程。
SQLite 数据库编程来存储我们的支出。
scoped_model 状态管理来简化我们的编程。
让我们开始编程 expense_calculator 应用程序。
在 Android Studio 中创建一个新的 Flutter 应用程序,命名为 expense_calculator。
打开 pubspec.yaml 并添加包依赖。
dependencies:
flutter:
sdk: flutter
sqflite: ^1.1.0
path_provider: ^0.5.0+1
scoped_model: ^1.0.1
intl: any
请注意以下几点:
sqflite 用于 SQLite 数据库编程。
path_provider 用于获取系统特定的应用程序路径。
scoped_model 用于状态管理。
intl 用于日期格式化。
Android Studio 将显示 pubspec.yaml 已更新的以下警报。
点击 Get dependencies 选项。Android Studio 将从互联网获取包并为应用程序正确配置。
删除 main.dart 中的现有代码。
添加新文件 Expense.dart 来创建 Expense class。Expense class 将具有以下属性和方法。
属性:id − 用于在 SQLite 数据库中表示支出条目的唯一 id。
属性:amount − 花费的金额。
属性:date − 花费金额的日期。
属性:category − 类别表示花费金额的领域,例如 Food、Travel 等。
formattedDate − 用于格式化 date 属性。
fromMap − 用于将数据库表中的字段映射到 expense 对象中的属性,并创建新的 expense 对象。
factory Expense.fromMap(Map<String, dynamic> data) {
return Expense(
data['id'],
data['amount'],
DateTime.parse(data['date']),
data['category']
);
}
toMap − 用于将 expense 对象转换为 Dart Map,可进一步用于数据库编程。
Map<String, dynamic> toMap() => {
"id" : id,
"amount" : amount,
"date" : date.toString(),
"category" : category,
};
columns − 表示数据库字段的静态变量。
将以下代码输入并保存到 Expense.dart 文件中。
import 'package:intl/intl.dart'; class Expense {
final int id;
final double amount;
final DateTime date;
final String category;
String get formattedDate {
var formatter = new DateFormat('yyyy-MM-dd');
return formatter.format(this.date);
}
static final columns = ['id', 'amount', 'date', 'category'];
Expense(this.id, this.amount, this.date, this.category);
factory Expense.fromMap(Map<String, dynamic> data) {
return Expense(
data['id'],
data['amount'],
DateTime.parse(data['date']), data['category']
);
}
Map<String, dynamic> toMap() => {
"id" : id,
"amount" : amount,
"date" : date.toString(),
"category" : category,
};
}
上述代码简单且易于理解。
添加新文件 Database.dart 来创建 SQLiteDbProvider class。SQLiteDbProvider class 的目的是:
使用 getAllExpenses 方法获取数据库中所有可用的支出。它将用于列出用户的所有支出信息。
Future<List<Expense>> getAllExpenses() async {
final db = await database;
List<Map> results = await db.query(
"Expense", columns: Expense.columns, orderBy: "date DESC"
);
List<Expense> expenses = new List();
results.forEach((result) {
Expense expense = Expense.fromMap(result);
expenses.add(expense);
});
return expenses;
}
使用 getExpenseById 方法根据数据库中可用的支出标识获取特定支出信息。它将用于向用户显示特定支出信息。
Future<Expense> getExpenseById(int id) async {
final db = await database;
var result = await db.query("Expense", where: "id = ", whereArgs: [id]);
return result.isNotEmpty ?
Expense.fromMap(result.first) : Null;
}
使用 getTotalExpense 方法获取用户的总支出。它将用于向用户显示当前总支出。
Future<double> getTotalExpense() async {
final db = await database;
List<Map> list = await db.rawQuery(
"Select SUM(amount) as amount from expense"
);
return list.isNotEmpty ? list[0]["amount"] : Null;
}
使用 insert 方法将新支出信息添加到数据库中。它将用于由用户向应用程序添加新的支出条目。
Future<Expense> insert(Expense expense) async {
final db = await database;
var maxIdResult = await db.rawQuery(
"SELECT MAX(id)+1 as last_inserted_id FROM Expense"
);
var id = maxIdResult.first["last_inserted_id"];
var result = await db.rawInsert(
"INSERT Into Expense (id, amount, date, category)"
" VALUES (?, ?, ?, ?)", [
id, expense.amount, expense.date.toString(), expense.category
]
);
return Expense(id, expense.amount, expense.date, expense.category);
}
使用 update 方法更新现有支出信息。它将用于由用户编辑和更新系统中现有的支出条目。
update(Expense product) async {
final db = await database;
var result = await db.update("Expense", product.toMap(),
where: "id = ?", whereArgs: [product.id]);
return result;
}
使用 delete 方法删除现有支出信息。它将用于由用户移除系统中现有的支出条目。
delete(int id) async {
final db = await database;
db.delete("Expense", where: "id = ?", whereArgs: [id]);
}
SQLiteDbProvider class 的完整代码如下:
import 'dart:async';
import 'dart:io';
import 'package:path/path.dart';
import 'package:path_provider/path_provider.dart';
import 'package:sqflite/sqflite.dart';
import 'Expense.dart';
class SQLiteDbProvider {
SQLiteDbProvider._();
static final SQLiteDbProvider db = SQLiteDbProvider._();
static Database _database; Future<Database> get database async {
if (_database != null)
return _database;
_database = await initDB();
return _database;
}
initDB() async {
Directory documentsDirectory = await getApplicationDocumentsDirectory();
String path = join(documentsDirectory.path, "ExpenseDB2.db");
return await openDatabase(
path, version: 1, onOpen:(db){}, onCreate: (Database db, int version) async {
await db.execute(
"CREATE TABLE Expense (
""id INTEGER PRIMARY KEY," "amount REAL," "date TEXT," "category TEXT""
)
");
await db.execute(
"INSERT INTO Expense ('id', 'amount', 'date', 'category')
values (?, ?, ?, ?)",[1, 1000, '2019-04-01 10:00:00', "Food"]
);
/*await db.execute(
"INSERT INTO Product ('id', 'name', 'description', 'price', 'image')
values (?, ?, ?, ?, ?)", [
2, "Pixel", "Pixel is the most feature phone ever", 800, "pixel.png"
]
);
await db.execute(
"INSERT INTO Product ('id', 'name', 'description', 'price', 'image')
values (?, ?, ?, ?, ?)", [
3, "Laptop", "Laptop is most productive development tool", 2000, "laptop.png"
]
);
await db.execute(
"INSERT INTO Product ('id', 'name', 'description', 'price', 'image')
values (?, ?, ?, ?, ?)", [
4, "Tablet", "Laptop is most productive development tool", 1500, "tablet.png"
]
);
await db.execute(
"INSERT INTO Product ('id', 'name', 'description', 'price', 'image')
values (?, ?, ?, ?, ?)", [
5, "Pendrive", "iPhone is the stylist phone ever", 100, "pendrive.png"
]
);
await db.execute(
"INSERT INTO Product ('id', 'name', 'description', 'price', 'image')
values (?, ?, ?, ?, ?)", [
6, "Floppy Drive", "iPhone is the stylist phone ever", 20, "floppy.png"
]
); */
}
);
}
Future<List<Expense>> getAllExpenses() async {
final db = await database;
List<Map>
results = await db.query(
"Expense", columns: Expense.columns, orderBy: "date DESC"
);
List<Expense> expenses = new List();
results.forEach((result) {
Expense expense = Expense.fromMap(result);
expenses.add(expense);
});
return expenses;
}
Future<Expense> getExpenseById(int id) async {
final db = await database;
var result = await db.query("Expense", where: "id = ", whereArgs: [id]);
return result.isNotEmpty ? Expense.fromMap(result.first) : Null;
}
Future<double> getTotalExpense() async {
final db = await database;
List<Map> list = await db.rawQuery(
"Select SUM(amount) as amount from expense"
);
return list.isNotEmpty