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。

  • 將預設啟動程式碼 (main.dart) 替換為我們的 product_rest_app 程式碼。

  • product_nav_app 中的 assets 資料夾複製到 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 的最新版本號替換任何

  • 在 pubspec.yaml 檔案中配置 path_provider 包,如下所示:

dependencies: path_provider: any
  • 這裡,path_provider 包用於獲取系統的臨時資料夾路徑和應用程式的路徑。使用 sqflite 的最新版本號替換 any

  • Android Studio 將提示 pubspec.yaml 已更新。

Updated
  • 點選“獲取依賴項”選項。Android Studio 將從網際網路獲取包併為應用程式正確配置它。

  • 在資料庫中,我們需要主鍵 id 作為附加欄位,以及產品屬性(如名稱、價格等)。因此,在 Product 類中新增 id 屬性。此外,新增一個新方法 toMap,用於將 product 物件轉換為 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 '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; 
}
  • 這裡,我們做了以下工作:

    • 使用 query 方法獲取所有產品資訊。query 提供了一種快捷方式來查詢表資訊,而無需編寫整個查詢。query 方法將使用我們的輸入(如列、orderBy 等)自行生成正確的查詢。

    • 使用 Product 的 fromMap 方法透過迴圈 results 物件來獲取產品詳細資訊,該物件儲存表中的所有行。

  • 編寫一個方法來獲取特定於 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、update 和 delete 方法,用於插入、更新和從資料庫刪除產品。

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]);
   } 
}
  • 更改 main 方法以獲取產品資訊。

void main() {
   runApp(MyApp(products: SQLiteDbProvider.db.getAllProducts())); 
}
  • 這裡,我們使用了 getAllProducts 方法從資料庫中獲取所有產品。

  • 執行應用程式並檢視結果。它將與之前的示例“訪問產品服務 API”類似,只是產品資訊儲存在本地 SQLite 資料庫中並從中獲取。

雲 Firestore

Firebase 是一個 BaaS 應用開發平臺。它提供了許多功能來加快移動應用程式的開發,例如身份驗證服務、雲端儲存等。Firebase 的主要功能之一是 Cloud Firestore,一個基於雲的即時 NoSQL 資料庫。

Flutter 提供了一個特殊的包 cloud_firestore 用於與 Cloud Firestore 程式設計。讓我們在 Cloud Firestore 中建立一個線上產品商店,並建立一個應用程式來訪問該產品商店。

  • 在 Android Studio 中建立一個新的 Flutter 應用程式,命名為 product_firebase_app。

  • 將預設啟動程式碼 (main.dart) 替換為我們的 product_rest_app 程式碼。

  • 將 product_rest_app 中的 Product.dart 檔案複製到 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'], 
      ); 
   }
}
  • 將 product_rest_app 中的 assets 資料夾複製到 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
  • 點選“獲取依賴項”選項。Android Studio 將從網際網路獲取包併為應用程式正確配置它。

  • 使用以下步驟在 Firebase 中建立一個專案:

    • 透過在 https://firebase.google.com/pricing/. 選擇免費計劃來建立一個 Firebase 帳戶。

    • 建立 Firebase 帳戶後,它將重定向到專案概述頁面。它列出了所有基於 Firebase 的專案,並提供了一個建立新專案的選項。

    • 點選“新增專案”,它將開啟一個專案建立頁面。

    • 輸入 products app db 作為專案名稱,然後點選“建立專案”選項。

    • 轉到 *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 
   } 
}

    這裡,外掛和類路徑用於讀取 google_service.json 檔案。

    • 開啟 android/app/build.gradle 幷包含以下程式碼。

android {
   defaultConfig { 
      ... 
      multiDexEnabled true 
   } 
   ...
}
dependencies {
   ... 
   compile 'com.android.support: multidex:1.0.3' 
}

    此依賴項使 Android 應用程式能夠使用多 dex 功能。

    • 按照 Firebase 控制檯中的其餘步驟操作,或者跳過。

  • 使用以下步驟在新建立的專案中建立一個產品商店:

    • 轉到 Firebase 控制檯。

    • 開啟新建立的專案。

    • 點選左側選單中的“資料庫”選項。

    • 點選“建立資料庫”選項。

    • 點選“以測試模式啟動”,然後點選“啟用”。

    • 點選“新增集合”。輸入 product 作為集合名稱,然後點選“下一步”。

    • 輸入此處影像中顯示的示例產品資訊:

Sample Product Information
  • 使用 新增文件 選項新增其他產品資訊。

  • 開啟 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 方法用於訪問雲端儲存中可用的 product 集合。Firestore.instance.collection 提供了許多選項來過濾集合以獲取必要的文件。但是,我們沒有應用任何過濾器來獲取所有產品資訊。

  • Cloud Firestore 透過 Dart Stream 概念提供集合,因此將 MyApp 和 MyHomePage widget 中的產品型別從 Future<list<Product>> 修改為 Stream<QuerySnapshot>。

  • 更改 MyHomePage widget 的 build 方法以使用 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> 型別獲取。由於我們的 widget ProductBoxList 與文件不相容,我們已將文件轉換為 List<Product> 型別,然後進一步使用它。

  • 最後,執行應用程式並檢視結果。由於我們使用了與 SQLite 應用程式 相同的產品資訊,並且僅更改了儲存介質,因此生成的應用程式與 SQLite 應用程式 應用程式看起來相同。

廣告
© . All rights reserved.