Flutter 怎么写高级应用?

文章导读
Previous Quiz Next 在本章中,我们将学习如何编写一个完整的移动应用程序 expense_calculator。该应用程序的目的是存储我们的支出信息。应用程序的完整功能如下:
A A

Flutter - 编写高级应用程序



Previous
Quiz
Next

在本章中,我们将学习如何编写一个完整的移动应用程序 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 已更新的以下警报。

Alert Writing Advanced Applications
  • 点击 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