Flutter - 数据库概念
Flutter 提供了许多用于处理数据库的高级软件包。最重要的软件包是 −
sqflite − 用于访问和操作 SQLite 数据库,以及
firebase_database − 用于访问和操作 Google 的云托管 NoSQL 数据库。
在本章中,我们将详细讨论它们中的每一个。
SQLite
SQLite 数据库是事实上的标准基于 SQL 的嵌入式数据库引擎。它是一个小型且久经考验的数据库引擎。sqflite 包提供了许多功能来有效地使用 SQLite 数据库。它提供了操作 SQLite 数据库引擎的标准方法。 sqflite 包提供的核心功能如下 −
创建/打开(openDatabase 方法)SQLite 数据库。
针对 SQLite 数据库执行 SQL 语句(execute 方法)。
高级查询方法(query 方法),以减少查询和从 SQLite 数据库获取信息所需的代码。
让我们创建一个产品应用程序,使用 sqflite 包从标准 SQLite 数据库引擎存储和获取产品信息,并了解 SQLite 数据库和 sqflite 包背后的概念。
在 Android Studio 中创建一个新的 Flutter 应用程序 product_sqlite_app。
用我们的 product_rest_app 代码替换默认启动代码(main.dart)。
复制将 assets 文件夹从 product_nav_app 移至 product_rest_app,并在 *pubspec.yaml` 文件中添加 assets。
flutter: assets: - assets/appimages/floppy.png - assets/appimages/iphone.png - assets/appimages/laptop.png - assets/appimages/pendrive.png - assets/appimages/pixel.png - assets/appimages/tablet.png
在pubspec.yaml文件中配置sqflite包,如下所示 −
dependencies: sqflite: any
使用最新版本的sqflite代替any
在pubspec.yaml文件中配置path_provider包,如下所示−
dependencies: path_provider: any
这里path_provider包用于获取系统的临时文件夹路径和应用程序的路径。使用最新版本的sqflite代替any。
Android Studio会提醒pubspec.yaml已更新。
![Updated](/flutter/images/updated.jpg)
单击获取依赖项选项。 Android Studio 将从 Internet 获取包,并为应用程序正确配置它。
在数据库中,我们需要主键、id 作为附加字段以及产品属性(如名称、价格等),因此,在 Product 类中添加 id 属性。此外,添加一个新方法 toMap,将产品对象转换为 Map 对象。fromMap 和 toMap 用于序列化和反序列化 Product 对象,并用于数据库操作方法。
class Product { final int id; final String name; final String description; final int price; final String image; static final columns = ["id", "name", "description", "price", "image"]; Product(this.id, this.name, this.description, this.price, this.image); factory Product.fromMap(Map<String, dynamic> data) { return Product( data['id'], data['name'], data['description'], data['price'], data['image'], ); } Map<String, dynamic> toMap() => { "id": id, "name": name, "description": description, "price": price, "image": image }; }
在 lib 文件夹中创建一个新文件 Database.dart,用于编写 SQLite 相关功能。
在 Database.dart 中导入必要的 import 语句。
import 'dart:async'; import 'dart:io'; import 'package:path/path.dart'; import 'package:path_provider/path_provider.dart'; import 'package:sqflite/sqflite.dart'; import 'Product.dart';
注意以下几点 −
async 用于编写异步方法。
io 用于访问文件和目录。
path 用于访问与文件路径相关的 dart 核心实用函数。
path_provider 用于获取临时和应用程序路径。
sqflite 用于操作 SQLite 数据库。
创建一个新类 SQLiteDbProvider
声明一个基于单例的静态 SQLiteDbProvider 对象,如下所示 −
class SQLiteDbProvider { SQLiteDbProvider._(); static final SQLiteDbProvider db = SQLiteDbProvider._(); static Database _database; }
可以通过静态 db 变量访问 SQLiteDBProvoider 对象及其方法。
SQLiteDBProvoider.db.<emthod>
创建一个获取数据库的方法(Future 选项),类型为 Future<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, "ProductDB.db"); return await openDatabase( path, version: 1, onOpen: (db) {}, onCreate: (Database db, int version) async { await db.execute( "CREATE TABLE Product (" "id INTEGER PRIMARY KEY," "name TEXT," "description TEXT," "price INTEGER," "image TEXT" ")" ); await db.execute( "INSERT INTO Product ('id', 'name', 'description', 'price', 'image') values (?, ?, ?, ?, ?)", [1, "iPhone", "iPhone is the stylist phone ever", 1000, "iphone.png"] ); 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", "Pendrive is useful storage medium", 100, "pendrive.png"] ); await db.execute( "INSERT INTO Product ('id', 'name', 'description', 'price', 'image') values (?, ?, ?, ?, ?)", [6, "Floppy Drive", "Floppy drive is useful rescue storage medium", 20, "floppy.png"] ); } ); }
这里,我们使用了以下方法 −
getApplicationDocumentsDirectory − 返回应用程序目录路径
join − 用于创建系统特定路径。我们已使用它来创建数据库路径。
openDatabase − 用于打开 SQLite 数据库
onOpen − 用于在打开数据库时编写代码
onCreate − 用于在首次创建数据库时编写代码
db.execute − 用于执行 SQL 查询。它接受查询。如果查询有占位符(?),则它接受第二个参数中的值作为列表。
编写一个方法来获取数据库中的所有产品 −
Future<List<Product>> getAllProducts() async { final db = await database; List<Map> results = await db.query("Product", columns: Product.columns, orderBy: "id ASC"); List<Product> products = new List(); results.forEach((result) { Product product = Product.fromMap(result); products.add(product); }); return products; }
在这里,我们完成了以下操作−
使用查询方法获取所有产品信息。查询提供了查询表信息的快捷方式,而无需编写整个查询。查询方法将使用我们的输入(如列、orderBy 等)自行生成适当的查询,
使用 Product 的 fromMap 方法通过循环结果对象来获取产品详细信息,该对象包含表中的所有行。
编写一种方法来获取特定于 id
的产品
Future<Product> getProductById(int id) async { final db = await database; var result = await db.query("Product", where: "id = ", whereArgs: [id]); return result.isNotEmpty ? Product.fromMap(result.first) : Null; }
在这里,我们使用 where 和 whereArgs 来应用过滤器。
创建三种方法 - 插入、更新和删除方法,以从数据库中插入、更新和删除产品。
insert(Product product) async { final db = await database; var maxIdResult = await db.rawQuery( "SELECT MAX(id)+1 as last_inserted_id FROM Product"); var id = maxIdResult.first["last_inserted_id"]; var result = await db.rawInsert( "INSERT Into Product (id, name, description, price, image)" " VALUES (?, ?, ?, ?, ?)", [id, product.name, product.description, product.price, product.image] ); return result; } update(Product product) async { final db = await database; var result = await db.update("Product", product.toMap(), where: "id = ?", whereArgs: [product.id]); return result; } delete(int id) async { final db = await database; db.delete("Product", where: "id = ?", whereArgs: [id]); }
Database.dart 最终代码如下 −
import 'dart:async'; import 'dart:io'; import 'package:path/path.dart'; import 'package:path_provider/path_provider.dart'; import 'package:sqflite/sqflite.dart'; import 'Product.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, "ProductDB.db"); return await openDatabase( path, version: 1, onOpen: (db) {}, onCreate: (Database db, int version) async { await db.execute( "CREATE TABLE Product (" "id INTEGER PRIMARY KEY," "name TEXT," "description TEXT," "price INTEGER," "image TEXT"")" ); await db.execute( "INSERT INTO Product ('id', 'name', 'description', 'price', 'image') values (?, ?, ?, ?, ?)", [1, "iPhone", "iPhone is the stylist phone ever", 1000, "iphone.png"] ); 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", "Pendrive is useful storage medium", 100, "pendrive.png"] ); await db.execute( "INSERT INTO Product ('id', 'name', 'description', 'price', 'image') values (?, ?, ?, ?, ?)", [6, "Floppy Drive", "Floppy drive is useful rescue storage medium", 20, "floppy.png"] ); } ); } Future<List<Product>> getAllProducts() async { final db = await database; List<Map> results = await db.query( "Product", columns: Product.columns, orderBy: "id ASC" ); List<Product> products = new List(); results.forEach((result) { Product product = Product.fromMap(result); products.add(product); }); return products; } Future<Product> getProductById(int id) async { final db = await database; var result = await db.query("Product", where: "id = ", whereArgs: [id]); return result.isNotEmpty ? Product.fromMap(result.first) : Null; } insert(Product product) async { final db = await database; var maxIdResult = await db.rawQuery("SELECT MAX(id)+1 as last_inserted_id FROM Product"); var id = maxIdResult.first["last_inserted_id"]; var result = await db.rawInsert( "INSERT Into Product (id, name, description, price, image)" " VALUES (?, ?, ?, ?, ?)", [id, product.name, product.description, product.price, product.image] ); return result; } update(Product product) async { final db = await database; var result = await db.update( "Product", product.toMap(), where: "id = ?", whereArgs: [product.id] ); return result; } delete(int id) async { final db = await database; db.delete("Product", where: "id = ?", whereArgs: [id]); } }
更改主要方法以获取产品信息。
void main() { runApp(MyApp(products: SQLiteDbProvider.db.getAllProducts())); }
在这里,我们使用 getAllProducts 方法从数据库中获取所有产品。
运行应用程序并查看结果。它与前面的示例访问产品服务 API类似,只是产品信息存储并从本地 SQLite 数据库中获取。
Cloud Firestore
Firebase 是一个 BaaS 应用开发平台。它提供了许多功能来加速移动应用程序开发,如身份验证服务、云存储等。Firebase 的主要功能之一是 Cloud Firestore,这是一个基于云的实时 NoSQL 数据库。
Flutter 提供了一个特殊的包 cloud_firestore 来使用 Cloud Firestore 进行编程。让我们在 Cloud Firestore 中创建一个在线产品商店,并创建一个应用程序来访问该产品商店。
在 Android Studio 中创建一个新的 Flutter 应用程序 product_firebase_app。
用我们的 product_rest_app 代码替换默认启动代码 (main.dart)。
将 Product.dart 文件从 product_rest_app 复制到 lib 文件夹中。
class Product { final String name; final String description; final int price; final String image; Product(this.name, this.description, this.price, this.image); factory Product.fromMap(Map<String, dynamic> json) { return Product( json['name'], json['description'], json['price'], json['image'], ); } }
将 assets 文件夹从 product_rest_app 复制到 product_firebase_app 并在 pubspec.yaml 文件中添加 assets。
flutter: assets: - assets/appimages/floppy.png - assets/appimages/iphone.png - assets/appimages/laptop.png - assets/appimages/pendrive.png - assets/appimages/pixel.png - assets/appimages/tablet.png
在pubspec.yaml文件中配置cloud_firestore包,如下所示−
dependencies: cloud_firestore: ^0.9.13+1
这里使用最新版本的cloud_firestore包。
Android Studio将提醒pubspec.yaml已更新,如下所示−
![Cloud Firestore Package](/flutter/images/cloud_firestore_package.jpg)
单击获取依赖项选项。 Android Studio 将从 Internet 获取软件包,并为应用程序正确配置它。
使用以下步骤在 Firebase 中创建项目 −
在 https://firebase.google.com/pricing/ 上选择免费计划,创建 Firebase 帐户。
创建 Firebase 帐户后,它将重定向到项目概览页面。它列出了所有基于 Firebase 的项目,并提供了创建新项目的选项。
单击"添加项目",它将打开一个项目创建页面。
输入产品应用程序数据库作为项目名称,然后单击"创建项目"选项。
转到 *Firebase 控制台。
单击项目概览。它将打开项目概览页面。
单击 android 图标。它将打开特定于 Android 开发的项目设置。
输入 Android 包名称,com.tutorialspoint.flutterapp.product_firebase_app。
单击注册应用。它会生成一个项目配置文件,google_service.json。
下载 google_service.json,然后将其移动到项目的 android/app 目录中。此文件是我们的应用程序与 Firebase 之间的连接。
打开 android/app/build.gradle 并包含以下代码 −
apply plugin: 'com.google.gms.google-services'
打开 android/build.gradle 并包含以下配置 −
buildscript { repositories { // ... } dependencies { // ... classpath 'com.google.gms:google-services:3.2.1' // new } }
打开 android/app/build.gradle 并包含以下代码。
此处,插件和类路径用于读取 google_service.json 文件。
android { defaultConfig { ... multiDexEnabled true } ... } dependencies { ... compile 'com.android.support: multidex:1.0.3' }
按照 Firebase 控制台中的其余步骤操作或跳过。
使用以下步骤在新创建的项目中创建产品商店 −
转到 Firebase 控制台。
打开新创建的项目。
单击左侧菜单中的数据库选项。
单击创建数据库选项。
单击以测试模式启动,然后单击启用。
单击添加集合。输入产品作为集合名称,然后单击下一步。
在此处输入如图所示的示例产品信息 −
此依赖项使 android 应用程序能够使用多个 dex 功能。
![示例产品信息](/flutter/images/sample_product_information.jpg)
使用添加文档选项添加其他产品信息。
打开 main.dart 文件并导入 Cloud Firestore 插件文件并删除 http 包。
import 'package:cloud_firestore/cloud_firestore.dart';
删除 parseProducts 并更新 fetchProducts 以从 Cloud Firestore 而不是产品服务 API 获取产品。
Stream<QuerySnapshot> fetchProducts() { return Firestore.instance.collection('product').snapshots(); }
此处,Firestore.instance.collection 方法用于访问云存储中可用的产品集合。Firestore.instance.collection 提供了许多选项来过滤集合以获取必要的文档。但是,我们没有应用任何过滤器来获取所有产品信息。
Cloud Firestore 通过 Dart Stream 概念提供集合,因此从 Future<list<Product>> 修改 MyApp 和 MyHomePage 小部件中的产品类型到 Stream<QuerySnapshot>。
将 MyHomePage 小部件的构建方法更改为使用 StreamBuilder 而不是 FutureBuilder。
@override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: Text("Product Navigation")), body: Center( child: StreamBuilder<QuerySnapshot>( stream: products, builder: (context, snapshot) { if (snapshot.hasError) print(snapshot.error); if(snapshot.hasData) { List<DocumentSnapshot> documents = snapshot.data.documents; List<Product> items = List<Product>(); for(var i = 0; i < documents.length; i++) { DocumentSnapshot document = documents[i]; items.add(Product.fromMap(document.data)); } return ProductBoxList(items: items); } else { return Center(child: CircularProgressIndicator()); } }, ), ) ); }
在这里,我们已将产品信息提取为 List<DocumentSnapshot> 类型。由于我们的小部件 ProductBoxList 与文档不兼容,因此我们将文档转换为 List<Product> 类型并进一步使用它。
最后,运行应用程序并查看结果。由于我们使用了与 SQLite 应用程序 相同的产品信息,并且仅更改了存储介质,因此生成的应用程序看起来与 SQLite 应用程序 应用程序完全相同。