從0開始設計Flutter獨立APP | 第一篇: 資料庫與狀態管理

lanistor發表於2020-06-18

鑑於Flutter高效能渲染和跨平臺的優勢,閃點清單在移動端APP上,使用了完整的Flutter框架來開發。既然是完整APP,架構搭建完全不受歷史Native APP的影響,沒有歷史包袱的沉澱,設計也能更靈活和健壯。

Flutter

首先列舉部分閃點清單的業務特性(較為通用的業務特性):

  1. 本地有較大資料量的清單資料,離線可用,未登入可用;登入後需要伺服器資料同步
  2. 狀態變更場景多,前端狀態邏輯較為複雜,跨頁面、跨元件狀態更新頻繁

這幾個業務點,設計到的技術選型有:本地資料庫、前端狀態管理,對很多業務來說,這幾點都是比較核心的東西,也是我們今天重點要講的內容。

資料庫選型

資料庫選型,首先要定的,就是選擇資料庫型別:關係型資料庫、非關係型資料庫還是Key/Value儲存。

對於關係型資料庫,可選的有比如:SQLite、Core Data、GreenDao等;對於非關係型資料庫,可選有Realm、UnQLite等;對於Key/Value儲存,有比如Redis、Berkeley DB、Level DB等。

由於業務形態具有複雜的查詢場景,所以首先排除了Key/Value儲存;然後鑑於業務迭代頻繁,資料結構變動較大,所以完全的關係型資料庫使用成本會較高,版本更新時在資料相容和資料清洗方面要做較多的工作;所以我們採用了NoSQL資料庫,或者支援JSON型別的關係型資料庫。

Flutter目前在NoSQL上的可選項並不多,Realm、UnQLite等均未支援(當然可以通過Flutter FFI來封裝給Dart用,但成本過高);Flutter的sqflite外掛可以較好地支援SQLite,但由於SQLite在3.9以後才支援JSON資料,考慮到Android版本(Android各版本使用的SQLite版本文件)相容問題,我們並沒有採用sqflite;我們最終採用的資料庫是Sembast,一個還比較小眾,但效能和API建設都還不錯的NoSQL資料庫。

Flutter

Sembast介紹

Sembast API很簡潔,但能支援較複雜的資料庫操作。
在資料查詢上,能夠通過簡單的邏輯API,通過聚合構造出複雜的邏輯查詢語句;資料排序實現也比較完整,支援多欄位排序(但不支援bool型別排序);對事務操作也有支援;支援整型自增key。

Sembast部分API預覽

var store = intMapStoreFactory.store('animals');

// 事務處理
await db.transaction((txn) async {
  await store.add(txn, {'name': 'fish'});
  await store.add(txn, {'name': 'cat'});
  await store.add(txn, {'name': 'dog'});
});

// 資料查詢
var finder = Finder(
    filter: Filter.greaterThan('name', 'cat'),
    sortOrders: [SortOrder('name')]);
var records = await store.find(db, finder: finder);

expect(records.length, 2);
expect(records[0]['name'], 'dog');
expect(records[1]['name'], 'fish');

但由於對小眾資料庫前途的擔憂考慮,我們設計了便於遷移的資料架構,對資料操作層做了一層抽象,後期如果遷移資料庫,業務層可以完全不需要改動。

狀態管理選型

狀態管理,在如今的前端技術中,是非常重要的一環,好的狀態管理框架,可以讓業務更好得解耦、簡化元件資料通訊成本、大幅提升開發體驗。我們在狀態管理選型上花了較多的時間來對比各種方案,比如:providerblocreduxscoped_modelmobx、甚至業界網紅團隊的fish-redux。我們最終採用的是mobx,關於各個方案的對比,一篇文章講不完,我們最終選擇mobx,更多是因為它的API更友好。

Mobx採用註解的方式來定義狀態,並封裝了一個Widget用於Widget的資料更新,學習、使用成本較低。

Tech

註解定義

Mobx的註解使用方式,與Web中的Vue非常類似如:

  1. 使用@observable來註解一個屬性,表示其需要被監聽,Mobx會自動為其新增getter和setter
  2. 使用@computed來註解一個計算屬性
  3. 使用@action來註解一個修改store的方法,類似於Vuex裡的mutations

示例程式碼:

import 'package:mobx/mobx.dart';

part 'counter.g.dart';

class Counter = CounterBase with _$Counter;

abstract class CounterBase with Store {
  @observable
  int value = 0;

  @computed
  int get allowCount {
    return value*2;
  }

  @action
  void increment() {
    value++;
  }
}

使用Mobx必須的一個步驟,就是前置編譯步驟。我們編寫Mobx的狀態指令碼,需要在前置編譯環節,編譯成dart可讀的指令碼,為此我們需要執行flutter pub run build_runner build生成.g.dart為字尾的檔案(其實就是將註解轉義為getter和setter)。
UI更新方式

UI更新方式

需要使用Mobx的Widget,使用Observer包裝一層,這樣其就可以相應Mobx的狀態變化,如:

Observer(
  builder: (context) => Text('暱稱: ${userStore.user.username}'),
)

前置編譯指令碼

Dart在編譯時和執行時均無法支援註解,所以Mobx使用了前置編譯指令碼解析註解動態生成Dart程式碼的方式(Dart官方支援)。Mobx註解方式使用前,需要為專案新增build_runnermobx_codegen依賴。build_runner為Dart官方提供,用於在構建專案之前執行特定任務,任務配置於依賴庫的build.yaml指令碼中; mobx_codegen依賴於該build_runner將註解生成可執行的Dart程式碼(如新增getter和setter方法)。

結尾

狀態管理和資料庫,是前端專案基礎框架的重要環節,設計好了可以很好地提升開發體驗和效率,降低後續開發維護成本。

講到這裡,還並沒有完成基礎框架的搭建,後面我們會講解更多的Flutter架構設計內容,比如:國際化、通知、分享、UI設計等等。


持續分享閃點清單在Flutter上的開發經驗。

相關文章