Flutter - Khái niệm cơ sở dữ liệu

Flutter cung cấp nhiều gói nâng cao để làm việc với cơ sở dữ liệu. Các gói quan trọng nhất là -

  • sqflite - Được sử dụng để truy cập và thao tác cơ sở dữ liệu SQLite, và

  • firebase_database - Được sử dụng để truy cập và thao tác với cơ sở dữ liệu NoSQL được lưu trữ trên đám mây từ Google.

Trong chương này, chúng ta hãy thảo luận chi tiết về từng loại trong số chúng.

SQLite

Cơ sở dữ liệu SQLite là công cụ cơ sở dữ liệu nhúng dựa trên SQL tiêu chuẩn và thực tế. Đây là công cụ cơ sở dữ liệu nhỏ và được kiểm tra thời gian. gói sqflite cung cấp rất nhiều chức năng để làm việc hiệu quả với cơ sở dữ liệu SQLite. Nó cung cấp các phương pháp tiêu chuẩn để thao tác cơ sở dữ liệu SQLite. Chức năng cốt lõi được cung cấp bởi gói sqflite như sau:

  • Tạo / Mở (phương pháp openDatabase) cơ sở dữ liệu SQLite.

  • Thực thi câu lệnh SQL (phương thức thực thi) trên cơ sở dữ liệu SQLite.

  • Phương pháp truy vấn nâng cao (phương pháp truy vấn) để giảm xuống mã cần thiết để truy vấn và lấy thông tin từ cơ sở dữ liệu SQLite.

Hãy để chúng tôi tạo một ứng dụng sản phẩm để lưu trữ và tìm nạp thông tin sản phẩm từ công cụ cơ sở dữ liệu SQLite tiêu chuẩn bằng cách sử dụng gói sqflite và hiểu khái niệm đằng sau cơ sở dữ liệu SQLite và gói sqflite.

  • Tạo ứng dụng Flutter mới trong Android studio, product_sqlite_app.

  • Thay thế mã khởi động mặc định (main.dart) bằng mã product_rest_app của chúng tôi .

  • Sao chép thư mục nội dung từ product_nav_app sang product_rest_app và thêm nội dung bên trong tệp * pubspec.yaml`.

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
  • Định cấu hình gói sqflite trong tệp pubspec.yaml như hình dưới đây -

dependencies: sqflite: any

Sử dụng số phiên bản mới nhất của sqflite thay cho bất kỳ

  • Định cấu hình gói path_provider trong tệp pubspec.yaml như hình dưới đây -

dependencies: path_provider: any
  • Ở đây, gói path_provider được sử dụng để lấy đường dẫn thư mục tạm thời của hệ thống và đường dẫn của ứng dụng. Sử dụng số phiên bản mới nhất của sqflite thay cho bất kỳ .

  • Android studio sẽ thông báo rằng pubspec.yaml đã được cập nhật.

  • Nhấp vào Nhận tùy chọn phụ thuộc. Android studio sẽ lấy gói từ Internet và định cấu hình đúng cách cho ứng dụng.

  • Trong cơ sở dữ liệu, chúng ta cần khóa chính, id làm trường bổ sung cùng với các thuộc tính Sản phẩm như tên, giá, v.v., Vì vậy, hãy thêm thuộc tính id trong lớp Sản phẩm. Ngoài ra, hãy thêm một phương thức mới, toMap để chuyển đổi đối tượng product thành đối tượng Map. fromMap và toMap được sử dụng để tuần tự hóa và hủy tuần tự hóa đối tượng Sản phẩm và nó được sử dụng trong các phương pháp thao tác cơ sở dữ liệu.

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 
   }; 
}
  • Tạo một tệp mới, Database.dart trong thư mục lib để viết chức năng liên quan đến SQLite .

  • Nhập câu lệnh nhập cần thiết trong 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';
  • Lưu ý những điểm sau ở đây -

    • async được sử dụng để viết các phương thức không đồng bộ.

    • io được sử dụng để truy cập các tệp và thư mục.

    • path được sử dụng để truy cập chức năng tiện ích lõi phi tiêu liên quan đến đường dẫn tệp.

    • path_provider được sử dụng để lấy tạm thời và đường dẫn ứng dụng.

    • sqflite được sử dụng để thao tác với cơ sở dữ liệu SQLite.

  • Tạo một lớp học mới SQLiteDbProvider

  • Khai báo một đối tượng SQLiteDbProvider tĩnh, dựa trên singleton như được chỉ định bên dưới:

class SQLiteDbProvider { 
   SQLiteDbProvider._(); 
   static final SQLiteDbProvider db = SQLiteDbProvider._(); 
   static Database _database; 
}
  • Đối tượng SQLiteDBProvoider và phương thức của nó có thể được truy cập thông qua biến db tĩnh.

SQLiteDBProvoider.db.<emthod>
  • Tạo một phương thức để lấy cơ sở dữ liệu (Tùy chọn tương lai) kiểu Tương lai <Cơ sở dữ liệu>. Tạo bảng sản phẩm và tải dữ liệu ban đầu trong quá trình tạo cơ sở dữ liệu.

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"]
         ); 
      }
   ); 
}
  • Ở đây, chúng tôi đã sử dụng các phương pháp sau:

    • getApplicationDocumentsDirectory - Trả về đường dẫn thư mục ứng dụng

    • join- Dùng để tạo đường dẫn cụ thể của hệ thống. Chúng tôi đã sử dụng nó để tạo đường dẫn cơ sở dữ liệu.

    • openDatabase - Được sử dụng để mở cơ sở dữ liệu SQLite

    • onOpen - Được sử dụng để viết mã trong khi mở cơ sở dữ liệu

    • onCreate - Được sử dụng để viết mã trong khi cơ sở dữ liệu được tạo lần đầu tiên

    • db.execute- Dùng để thực thi các truy vấn SQL. Nó chấp nhận một truy vấn. Nếu truy vấn có trình giữ chỗ (?), Thì nó chấp nhận các giá trị dưới dạng danh sách trong đối số thứ hai.

  • Viết một phương thức để lấy tất cả các sản phẩm trong cơ sở dữ liệu -

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; 
}
  • Ở đây, chúng tôi đã thực hiện như sau:

    • Đã sử dụng phương pháp truy vấn để tìm nạp tất cả thông tin sản phẩm. truy vấn cung cấp lối tắt để truy vấn thông tin bảng mà không cần viết toàn bộ truy vấn. phương thức truy vấn sẽ tự tạo truy vấn thích hợp bằng cách sử dụng đầu vào của chúng tôi như cột, orderBy, v.v.,

    • Đã sử dụng phương thức fromMap của Sản phẩm để lấy chi tiết sản phẩm bằng cách lặp lại đối tượng kết quả, đối tượng này chứa tất cả các hàng trong bảng.

  • Viết một phương pháp để có được sản phẩm cụ thể cho 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; 
}
  • Ở đây, chúng tôi đã sử dụng where và whereArgs để áp dụng bộ lọc.

  • Tạo ba phương thức - chèn, cập nhật và phương thức xóa để chèn, cập nhật và xóa sản phẩm khỏi cơ sở dữ liệu.

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]); 
}
  • Mã cuối cùng của Database.dart như sau:

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]);
   } 
}
  • Thay đổi phương thức chính để lấy thông tin sản phẩm.

void main() {
   runApp(MyApp(products: SQLiteDbProvider.db.getAllProducts())); 
}
  • Ở đây, chúng tôi đã sử dụng phương thức getAllProducts để tìm nạp tất cả các sản phẩm từ cơ sở dữ liệu.

  • Chạy ứng dụng và xem kết quả. Nó sẽ tương tự như ví dụ trước, Truy cập API dịch vụ sản phẩm , ngoại trừ thông tin sản phẩm được lưu trữ và tìm nạp từ cơ sở dữ liệu SQLite cục bộ.

Cloud Firestore

Firebase là một nền tảng phát triển ứng dụng BaaS. Nó cung cấp nhiều tính năng để tăng tốc độ phát triển ứng dụng di động như dịch vụ xác thực, lưu trữ đám mây, v.v. Một trong những tính năng chính của Firebase là Cloud Firestore, một cơ sở dữ liệu NoSQL thời gian thực dựa trên đám mây.

Flutter cung cấp một gói đặc biệt, cloud_firestore để lập trình với Cloud Firestore. Hãy để chúng tôi tạo cửa hàng sản phẩm trực tuyến trong Cloud Firestore và tạo ứng dụng để truy cập cửa hàng sản phẩm.

  • Tạo một ứng dụng Flutter mới trong Android studio, product_firebase_app.

  • Thay thế mã khởi động mặc định (main.dart) bằng mã product_rest_app của chúng tôi .

  • Sao chép tệp Product.dart từ product_rest_app vào thư mục 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'], 
      ); 
   }
}
  • Sao chép thư mục nội dung từ product_rest_app sang product_firebase_app và thêm nội dung bên trong tệp pubspec.yaml.

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
  • Định cấu hình gói cloud_firestore trong tệp pubspec.yaml như hình dưới đây -

dependencies: cloud_firestore: ^0.9.13+1
  • Tại đây, hãy sử dụng phiên bản mới nhất của gói cloud_firestore.

  • Android studio sẽ thông báo rằng pubspec.yaml được cập nhật như được hiển thị ở đây -

  • Nhấp vào Nhận tùy chọn phụ thuộc. Android studio sẽ lấy gói từ Internet và định cấu hình đúng cách cho ứng dụng.

  • Tạo một dự án trong Firebase bằng các bước sau:

    • Tạo tài khoản Firebase bằng cách chọn Gói miễn phí tại https://firebase.google.com/pricing/.

    • Khi tài khoản Firebase được tạo, nó sẽ chuyển hướng đến trang tổng quan của dự án. Nó liệt kê tất cả dự án dựa trên Firebase và cung cấp một tùy chọn để tạo một dự án mới.

    • Nhấp vào Thêm dự án và nó sẽ mở ra một trang tạo dự án.

    • Nhập db ứng dụng sản phẩm làm tên dự án và nhấp vào Tùy chọn tạo dự án.

    • Đi tới bảng điều khiển * Firebase.

    • Nhấp vào Tổng quan dự án. Nó mở ra trang tổng quan của dự án.

    • Nhấp vào biểu tượng android. Nó sẽ mở cài đặt dự án cụ thể cho phát triển Android.

    • Nhập tên Gói Android, com.tutorialspoint.flutterapp.product_firebase_app.

    • Nhấp vào Đăng ký ứng dụng. Nó tạo tệp cấu hình dự án, google_service.json.

    • Tải xuống google_service.json và sau đó di chuyển nó vào thư mục android / app của dự án. Tệp này là kết nối giữa ứng dụng của chúng tôi và Firebase.

    • Mở android / app / build.gradle và bao gồm mã sau:

apply plugin: 'com.google.gms.google-services'
    • Mở android / build.gradle và bao gồm cấu hình sau:

buildscript {
   repositories { 
      // ... 
   } 
   dependencies { 
      // ... 
      classpath 'com.google.gms:google-services:3.2.1' // new 
   } 
}

    Ở đây, plugin và đường dẫn lớp được sử dụng cho mục đích đọc tệp google_service.json.

    • Mở android / app / build.gradle và bao gồm cả mã sau.

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

    Sự phụ thuộc này cho phép ứng dụng android sử dụng nhiều chức năng dex.

    • Làm theo các bước còn lại trong Bảng điều khiển Firebase hoặc bỏ qua.

  • Tạo cửa hàng sản phẩm trong dự án mới tạo bằng cách sử dụng các bước sau:

    • Đi tới bảng điều khiển Firebase.

    • Mở dự án mới tạo.

    • Nhấp vào tùy chọn Cơ sở dữ liệu trong menu bên trái.

    • Nhấp vào Tùy chọn tạo cơ sở dữ liệu.

    • Nhấp vào Bắt đầu ở chế độ thử nghiệm và sau đó nhấp vào Bật.

    • Nhấp vào Thêm bộ sưu tập. Nhập sản phẩm làm tên bộ sưu tập và sau đó nhấp vào Tiếp theo.

    • Nhập thông tin sản phẩm mẫu như trong ảnh tại đây -

  • Thêm thông tin sản phẩm bổ sung bằng cách sử dụng Tùy chọn thêm tài liệu .

  • Mở tệp main.dart và nhập tệp plugin Cloud Firestore và xóa gói http.

import 'package:cloud_firestore/cloud_firestore.dart';
  • Xóa parseProducts và cập nhật fetchProducts để tìm nạp sản phẩm từ Cloud Firestore thay vì API dịch vụ sản phẩm.

Stream<QuerySnapshot> fetchProducts() { 
   return Firestore.instance.collection('product').snapshots(); }
  • Ở đây, phương thức Firestore.instance.collection được sử dụng để truy cập bộ sưu tập sản phẩm có sẵn trong cửa hàng đám mây. Firestore.instance.collection cung cấp nhiều tùy chọn lọc bộ sưu tập để lấy các tài liệu cần thiết. Nhưng, chúng tôi chưa áp dụng bất kỳ bộ lọc nào để lấy tất cả thông tin sản phẩm.

  • Cloud Firestore cung cấp bộ sưu tập thông qua khái niệm Dart Stream và do đó sửa đổi loại sản phẩm trong tiện ích MyApp và MyHomePage từ Future <list <Product>> thành Stream <QuerySnapshot>.

  • Thay đổi phương pháp xây dựng của tiện ích MyHomePage để sử dụng StreamBuilder thay vì 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()); 
               }
            }, 
         ), 
      )
   ); 
}
  • Ở đây, chúng tôi đã tìm nạp thông tin sản phẩm dưới dạng loại Danh sách <DocumentSnapshot>. Vì tiện ích con của chúng tôi, ProductBoxList không tương thích với tài liệu, chúng tôi đã chuyển đổi tài liệu thành loại Danh sách <Sản phẩm> và tiếp tục sử dụng nó.

  • Cuối cùng, chạy ứng dụng và xem kết quả. Vì chúng tôi đã sử dụng thông tin sản phẩm giống như thông tin của ứng dụng SQLite và chỉ thay đổi phương tiện lưu trữ, ứng dụng kết quả trông giống với ứng dụng ứng dụng SQLite .