使用 ISAR 資料庫提供離線 Flutter 支援

會煮咖啡的貓發表於2022-11-28

使用 ISAR 資料庫提供離線 Flutter 支援

譯文 https://medium.com/@tkarmakar...

前言

這是我的口頭禪,我試圖遵循我的 應用 application 。對於那些針對二三線城市的面向客戶的應用程式,應優先考慮離線支援。

我們可以使用像 SQLite 這樣的關聯式資料庫,也可以使用由 Hive、甚至 Isar 提供的非關聯式資料庫。

在這個部落格中,我們將深入研究工作原理,瞭解 Isar 資料庫的使用過程和易用性。

正文

什麼是 Isar 資料庫?

ISAR 資料庫是一個超快速的跨平臺 Flutter 資料庫。

以下是 Isar 的一些特色和亮點,

  • 為 Flutter 而生
  • 高度可 extension
  • 特色豐富
  • 支援全文搜尋
  • ACID 語義學
  • 靜態型別
  • Something 非同步
  • 開放原始碼

實施

讓我們看看如何可以輕鬆地實現在我們的 Flutter 應用程式 Isar 資料庫。

首先,我們必須瞭解我們的應用程式應該能夠執行什麼。

故事時間。

對於本教程,我們有一個藥物庫存應用程式,是由代表使用新增,刪除和更新他們的藥物庫存。

假設該代表將訪問偏遠地區銷售這種藥物,我們必須實施完整的離線支援,使他可以執行所有的過程離線和資料得到自動更新,當有網際網路連線。

資料流

  • 應用程式啟動並檢查資料。如果是,它從資料庫中獲取所有藥物並儲存在 Isar。如果沒有,它將從 Isar 獲取資料並填充資料。
  • 儲存在 ISAR 資料庫中的資料包含一個 isSynces 標誌,該標誌表示資料與 firebase 同步的天氣。
  • 每當一種新的藥物被新增,如果有網際網路,它會同時更新 Isar 和火力基地,否則它會更新 Isar 與 isSynced 標誌為假。
  • 剪輯也一樣。
  • 刪除每個刪除的專案將新增到一個列表中,並從 Isar 刪除。一旦有了連線,它就會更新資料庫。
  • 為了使資料始終保持同步,每隔 30 秒使用一個定時器檢查連線情況,並且整個資料庫與線上資料庫同步。

我們開始程式設計吧

我將假設您已經建立了該專案和整合的 Firebase,因此我們有權訪問 Firebase 的 firestore。

讓我們整合網路檢查器,以便讓連線狀態始終存在。

為此我們將使用,

  • connectivity_plus

https://pub.dev/packages/conn...

還有

  • internet_connection_checker

https://pub.dev/packages/inte...

main.dart , Flutter 程式碼,

// Import the firebase_core plugin
import 'dart:async';
import 'dart:developer';

import 'package:connectivity_plus/connectivity_plus.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:flutter/material.dart';
import 'package:get/route_manager.dart';
import 'package:internet_connection_checker/internet_connection_checker.dart';
import 'package:medicine_inventory/controller/inventory_controller.dart';
import 'package:medicine_inventory/database/isar_helper.dart';
import 'package:medicine_inventory/screen/inventory_list_page.dart';
import 'package:provider/provider.dart';

ValueNotifier<bool> isDeviceConnected = ValueNotifier(false);

void main() {
  WidgetsFlutterBinding.ensureInitialized();
  runApp(const App());
}

class App extends StatefulWidget {
  const App({Key? key}) : super(key: key);

  @override
  _AppState createState() => _AppState();
}

class _AppState extends State<App> {
  final Future<FirebaseApp> _initialization = Firebase.initializeApp();
  late StreamSubscription<ConnectivityResult> subscription;
  @override
  void initState() {
    IsarHelper.instance.init();
    super.initState();
    subscription = Connectivity()
        .onConnectivityChanged
        .listen((ConnectivityResult result) async {
      isDeviceConnected.value = await InternetConnectionChecker().hasConnection;
      log("Internet status ====== $isDeviceConnected");
    });
  }

  @override
  void dispose() {
    subscription.cancel();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return MultiProvider(
      providers: [
        ChangeNotifierProvider(
          create: (_) => InventoryController(),
        ),
      ],
      child: const GetMaterialApp(
        home: InventoryListPage(),
      ),
    );
  }
}

//something went wrong
class SomethingWentWrong extends StatelessWidget {
  const SomethingWentWrong({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return const Scaffold(
      body: Center(
        child: Text('Something went wrong \nPlease try again',textAlign: TextAlign.center,),
      ),
    );
  }
}

這裡,我們正在建立和 valueListable 變數,每次連通性發生變化時,它都會發生變化。

讀取資料

一旦應用程式啟動,我們將需要從線上或離線來源的藥物清單。我們將等待一秒鐘,根據網路狀態,從 firebase 獲取資料並儲存來自 Isar 的最新資料或負載。

//get inventory list
  Future<void> getInventoryList({bool showLoading = true}) async {
    showLoading ? startLoading() : null;
    _inventoryList = [];

    await Future.delayed(const Duration(seconds: 1)).then((value) async {
      if (isDeviceConnected.value) {
        _inventoryList = await _cloudFirestoreHelper.getAllMedicine();

        IsarHelper.instance.insertFresh(_inventoryList);
      } else {
        _inventoryList = await IsarHelper.instance.getItems();
      }
    });

    stopLoading();
    notifyListeners();
  }

新增資料

為了建立/新增藥物,我們將遵循相同的方法,我們將建立物件並檢查網路狀態(如果存在) ,issync 將為 true,並將在 Isar 和 firebase 中儲存。

  void addInventory(Inventory inventory) async {
    inventory.isSynced = isDeviceConnected.value;
    int id = await IsarHelper.instance.insertOne(inventory);
    inventory.id = id;
    _inventoryList.add(inventory);
    if (isDeviceConnected.value) {
      _cloudFirestoreHelper.addMedicine(inventory.toJson());
    }
    notifyListeners();
  }

更新資料

我更新了簡單的更新機制,我只是更新數量和儲存在 Isar 或消防基地。

  void updateMedicine(int index) async {
    _inventoryList[index].quantity = _inventoryList[index].quantity! + 1;
    _inventoryList[index].isSynced = isDeviceConnected.value;
    int id = await IsarHelper.instance.insertOne(_inventoryList[index]);
    _inventoryList[index].id = id;
    if (isDeviceConnected.value) {
      _cloudFirestoreHelper.updateMedicine(_inventoryList[index]);
    }
    notifyListeners();
  }

刪除資料

在刪除資料的情況下,我們將刪除的資料儲存在一個列表中,以防沒有連線,一旦資料同步,列表就會被清除。

void removeMedicine(Inventory inventory) async {
    inventory.isSynced = false;
    await IsarHelper.instance.removeItem(inventory);
    _inventoryList
        .removeWhere((element) => element.code_value == inventory.code_value);
    if (isDeviceConnected.value) {
      _cloudFirestoreHelper.removeInventory(inventory);
    } else {
      deletedMedicines.add(inventory);
    }
    notifyListeners();
  }

與 Firebase 同步

最後一件事是不時更新資料,以便保持資料的最新性。

  void checkIsSynced() async {
    List<Inventory> unsyncedMedicines =
        await IsarHelper.instance.getUnsyncedData();
    if (deletedMedicines.isNotEmpty) {
      for (Inventory element in deletedMedicines) {
        _cloudFirestoreHelper.removeInventory(element);
      }
      deletedMedicines.clear();
    }
    if (unsyncedMedicines.isNotEmpty) {
      for (Inventory element in unsyncedMedicines) {
        element.isSynced = true;
        await _cloudFirestoreHelper.updateMedicine(element);
        IsarHelper.instance.updateSync(element);
      }
    }
    getInventoryList(showLoading: false);
  }

我們已經完成了 CRUD 功能,現在我們將看到我們如何在 Isar 資料庫中做同樣的事情。

首先,我們建立了一個 helper singleton 類,

class IsarHelper {
  IsarHelper._privateConstructor();
  static final IsarHelper _instance = IsarHelper._privateConstructor();
  static IsarHelper get instance => _instance;

  late Isar isarInstance;

  init() async {
    isarInstance = await Isar.open([InventorySchema]);
  }
}
view raw

一旦我們建立了 singleton,我們將開啟 Isar 資料庫的一個例項並傳遞模式。

什麼是 schema?

Schema 是在資料庫中儲存資料時遵循的模式。

建立模式,我們將建立一個帶有必需變數的模型。

import 'package:isar/isar.dart';
part 'inventory.g.dart';

@collection
class Inventory {
  Id id = Isar.autoIncrement;
  String? authorizedBy;
  String? code_value;
  String? description;
  DateTime? expiryDate;
  String? hospitalId;
  String? mobile;
  String? productName;
  String? productType;
  int? quantity;
  String? status;
  String? unitCost;
  bool isSynced;

  Inventory({
    this.authorizedBy,
    this.code_value,
    this.description,
    this.expiryDate,
    this.hospitalId,
    this.mobile,
    this.productName,
    this.productType,
    this.quantity,
    this.status,
    this.unitCost,
    this.isSynced = true,
  });

  factory Inventory.fromJson(json) {
    return Inventory(
      authorizedBy: json['authorizedBy'],
      code_value: json['code_value'],
      description: json['description'],
      expiryDate: DateTime.parse(json['expiryDate']),
      hospitalId: json['hospitalId'],
      mobile: json['mobile'],
      productName: json['productName'],
      productType: json['productType'],
      quantity: json['quantity'],
      status: json['status'],
      unitCost: json['unitCost'],
    );
  }

  Map<String, dynamic> toJson() {
    return {
      "id": id,
      'authorizedBy': authorizedBy,
      'code_value': code_value,
      'description': description,
      'expiryDate': expiryDate!.toIso8601String(),
      'hospitalId': hospitalId,
      'mobile': mobile,
      'productName': productName,
      'productType': productType,
      'quantity': quantity,
      'status': status,
      'unitCost': unitCost,
    };
  }
}

我們需要使用@Collection 標記來構建模式。

我們將使用 build_runner 包生成程式碼。

https://pub.dev/packages/buil...

接下來,我們將看到所有 CRUD 函式。

如果你仔細閱讀程式碼,你會發現,對於寫入 Isar 資料庫,我們正在將整個事務包裝在一個事務中,以便順利地進行更改。這是 Isar 檔案所建議的。

InventorySheme 是由生成器函式建立的。

讓我們看看程式碼,

import 'package:isar/isar.dart';
import 'package:medicine_inventory/model/inventory.dart';

class IsarHelper {
  IsarHelper._privateConstructor();
  static final IsarHelper _instance = IsarHelper._privateConstructor();
  static IsarHelper get instance => _instance;

  late Isar isarInstance;

  init() async {
    isarInstance = await Isar.open([InventorySchema]);
  }

  insertFresh(List<Inventory> inventoryList) async {
    await isarInstance.writeTxn(() async {
      await isarInstance.clear();
      for (Inventory element in inventoryList) {
        await isarInstance.inventorys.put(element);
      }
    });
  }

  insertOne(Inventory inventoryItem) async {
    late int id;
    await isarInstance.writeTxn(() async {
      id = await isarInstance.inventorys.put(inventoryItem);
    });
    return id;
  }

  getItems() async {
    IsarCollection<Inventory> medicineCollection =
        isarInstance.collection<Inventory>();
    List<Inventory?> medicines = await medicineCollection.where().findAll();
    return medicines;
  }

  removeItem(Inventory inventory) async {
    await isarInstance.writeTxn(() async {
      await isarInstance.inventorys.delete(inventory.id);
    });
  }

  void updateSync(Inventory inventory) async {
    inventory.isSynced = true;
    await isarInstance.writeTxn(() async {
      await isarInstance.inventorys.put(inventory);
    });
  }

  getUnsyncedData() async {
    IsarCollection<Inventory> medicineCollection =
        isarInstance.collection<Inventory>();
    List<Inventory?> medicines =
        await medicineCollection.filter().isSyncedEqualTo(false).findAll();
    return medicines;
  }
}

如果你已經走了這麼遠,

恭喜你使用 Isar 理解了離線資料庫使用的大部分概念。

結束語

如果本文對你有幫助,請轉發讓更多的朋友閱讀。

也許這個操作只要你 3 秒鐘,對我來說是一個激勵,感謝。

祝你有一個美好的一天~

貓哥課程


© 貓哥

本文由mdnice多平臺釋出

相關文章