Flutter - концепции базы данных

Flutter предоставляет множество расширенных пакетов для работы с базами данных. Самые важные пакеты -

  • sqflite - Используется для доступа и управления базой данных SQLite, а также

  • firebase_database - Используется для доступа к базе данных NoSQL, размещенной в облаке, и управления ею от Google.

В этой главе давайте подробно обсудим каждый из них.

SQLite

База данных SQLite - это де-факто и стандартный встроенный механизм базы данных на основе SQL. Это небольшой и проверенный временем движок базы данных. Пакет sqflite предоставляет множество функций для эффективной работы с базой данных SQLite. Он предоставляет стандартные методы для управления ядром базы данных SQLite. Основные функции, предоставляемые пакетом sqflite, следующие:

  • Создать / открыть (метод openDatabase) базу данных SQLite.

  • Выполнить инструкцию SQL (метод выполнения) для базы данных SQLite.

  • Расширенные методы запроса (метод запроса) для сокращения кода, необходимого для запроса и получения информации из базы данных SQLite.

Давайте создадим приложение продукта для хранения и извлечения информации о продукте из стандартного механизма базы данных SQLite с использованием пакета sqflite и разберемся с концепцией базы данных SQLite и пакета sqflite.

  • Создайте новое приложение Flutter в студии Android, product_sqlite_app.

  • Замените код запуска по умолчанию (main.dart) нашим кодом product_rest_app .

  • Скопируйте папку с ресурсами из product_nav_app в product_rest_app и добавьте ресурсы в файл * 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
  • Настройте пакет sqflite в файле pubspec.yaml, как показано ниже -

dependencies: sqflite: any

Используйте последнюю версию sqflite вместо любых

  • Настройте пакет path_provider в файле pubspec.yaml, как показано ниже -

dependencies: path_provider: any
  • Здесь пакет path_provider используется для получения пути к временной папке системы и пути к приложению. Используйте номер последней версии sqflite вместо любого .

  • Студия Android сообщит, что pubspec.yaml обновлен.

  • Щелкните параметр Получить зависимости. Студия Android получит пакет из Интернета и правильно настроит его для приложения.

  • В базе данных нам нужен первичный ключ, id в качестве дополнительного поля вместе со свойствами продукта, такими как имя, цена и т. Д. Итак, добавьте свойство 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 
   }; 
}
  • Создайте новый файл Database.dart в папке lib для записи функций, связанных с 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; 
}
  • Доступ к объекту SQLiteDBProvoider и его методу можно получить через статическую переменную db.

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 метод сам сгенерирует правильный запрос, используя наши входные данные, такие как columns, 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 и создадим приложение для доступа к магазину продуктов.

  • Создайте новое приложение Flutter в студии Android, product_firebase_app.

  • Замените код запуска по умолчанию (main.dart) нашим кодом product_rest_app .

  • Скопируйте файл 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'], 
      ); 
   }
}
  • Скопируйте папку ресурсов из product_rest_app в product_firebase_app и добавьте ресурсы в файл 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
  • Настройте пакет cloud_firestore в файле pubspec.yaml, как показано ниже -

dependencies: cloud_firestore: ^0.9.13+1
  • Здесь используйте последнюю версию пакета cloud_firestore.

  • Студия Android сообщит, что pubspec.yaml обновлен, как показано здесь -

  • Щелкните параметр Получить зависимости. Студия Android получит пакет из Интернета и правильно настроит его для приложения.

  • Создайте проект в Firebase, выполнив следующие действия:

    • Создайте учетную запись Firebase, выбрав бесплатный план на https://firebase.google.com/pricing/.

    • После создания учетной записи 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 
   } 
}

    Здесь подключаемый модуль и путь к классу используются для чтения файла google_service.json.

    • Откройте android / app / build.gradle и включите также следующий код.

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

    Эта зависимость позволяет приложению Android использовать несколько функций dex.

    • Выполните оставшиеся шаги в консоли Firebase или просто пропустите их.

  • Создайте магазин продуктов во вновь созданном проекте, выполнив следующие действия:

    • Перейдите в консоль Firebase.

    • Откройте только что созданный проект.

    • Щелкните опцию База данных в левом меню.

    • Нажмите кнопку «Создать базу данных».

    • Нажмите Пуск в тестовом режиме, а затем - Включить.

    • Щелкните Добавить коллекцию. Введите продукт в качестве названия коллекции и нажмите Далее.

    • Введите информацию об образце продукта, как показано на изображении здесь -

  • Добавьте дополнительную информацию о продукте с помощью параметров Добавить документ .

  • Откройте файл 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, поэтому измените тип продуктов в MyApp и MyHomePage виджетах с Future <list <Product>> на 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 .