鑑於Flutter高效能渲染和跨平臺的優勢,閃點清單在移動端APP上,使用了完整的Flutter框架來開發。既然是完整APP,架構搭建完全不受歷史Native APP的影響,沒有歷史包袱的沉澱,設計也能更靈活和健壯。
首先列舉部分閃點清單的業務特性(較為通用的業務特性):
- 本地有較大資料量的清單資料,離線可用,未登入可用;登入後需要伺服器資料同步
- 狀態變更場景多,前端狀態邏輯較為複雜,跨頁面、跨元件狀態更新頻繁
這幾個業務點,設計到的技術選型有:本地資料庫、前端狀態管理,對很多業務來說,這幾點都是比較核心的東西,也是我們今天重點要講的內容。
資料庫選型
資料庫選型,首先要定的,就是選擇資料庫型別:關係型資料庫、非關係型資料庫還是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資料庫。
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');
但由於對小眾資料庫前途的擔憂考慮,我們設計了便於遷移的資料架構,對資料操作層做了一層抽象,後期如果遷移資料庫,業務層可以完全不需要改動。
狀態管理選型
狀態管理,在如今的前端技術中,是非常重要的一環,好的狀態管理框架,可以讓業務更好得解耦、簡化元件資料通訊成本、大幅提升開發體驗。我們在狀態管理選型上花了較多的時間來對比各種方案,比如:provider、bloc、redux、scoped_model、mobx、甚至業界網紅團隊的fish-redux。我們最終採用的是mobx,關於各個方案的對比,一篇文章講不完,我們最終選擇mobx,更多是因為它的API更友好。
Mobx採用註解的方式來定義狀態,並封裝了一個Widget用於Widget的資料更新,學習、使用成本較低。
註解定義
Mobx的註解使用方式,與Web中的Vue非常類似如:
- 使用@observable來註解一個屬性,表示其需要被監聽,Mobx會自動為其新增getter和setter
- 使用@computed來註解一個計算屬性
- 使用@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_runner
和mobx_codegen
依賴。build_runner為Dart官方提供,用於在構建專案之前執行特定任務,任務配置於依賴庫的build.yaml
指令碼中; mobx_codegen
依賴於該build_runner
將註解生成可執行的Dart程式碼(如新增getter和setter方法)。
結尾
狀態管理和資料庫,是前端專案基礎框架的重要環節,設計好了可以很好地提升開發體驗和效率,降低後續開發維護成本。
講到這裡,還並沒有完成基礎框架的搭建,後面我們會講解更多的Flutter架構設計內容,比如:國際化、通知、分享、UI設計等等。
持續分享閃點清單在Flutter上的開發經驗。