前幾天已經成功地讓使用者即使在沒有網路的情況下,也能安心地規劃行程。但我開始思考,一個完美的行程,如果只能自己用,是不是有點可惜?
我想做的,不只是讓 App 在離線時能夠運作,而是要讓它能打破裝置的界線,真正實現資料的自由流通。
今天,將實作分享功能,這意味著將建立一個完整的匯入與匯出流程。這將允許使用者將辛苦規劃的行程打包成一個檔案,輕鬆分享給旅伴~
要讓資料能夠被分享,第一步就是將它從資料庫中「取出」並轉換成一個通用格式。我選擇使用 JSON,因為它輕量且易於跨平台傳輸。這個過程主要分為三個步驟:
為了匯出一個完整的行程,需要一個函式,將 Drift 資料庫中的關聯資料,轉換成一個適合 JSON 輸出的物件。此外,在進行資料匯出與匯入時,除了基本的流程外,還有一些關鍵細節需要注意,以確保資料的完整性與相容性。
資料格式與型別轉換
null
的處理至關重要。建議在將資料庫的空值轉換成 JSON 時,為前端必填欄位補上預設值,避免應用程式出錯。效能與架構考量
JOIN
或一次性 IN
查詢,再在記憶體中將資料組合成巢狀結構,以大幅提升效能,這部分在 Day 17 - Drift CRUD 入門 中已有詳細介紹,這裡便不再贅述。schemaVersion
或 appVersion
欄位,方便在匯入時進行相容性檢查與處理。share_plus
套件分享檔案要讓使用者能夠將行程匯出成 JSON 檔案並分享,將使用 share_plus
套件。首先,在你的 pubspec.yaml
檔案中加入這個套件:
dependencies:
share_plus: ^11.1.0
接著,可以參考以下程式碼來實作匯出與分享的邏輯:
/// 匯出指定行程為 JSON 檔,並透過 share_plus 分享出去
Future<void> exportTripAsJson(int tripId, String tripName) async {
// 1. 從資料庫取出指定行程的完整資料(含活動與子活動)
final exportData = await db.tripsDao.getTripDataForExport(tripId);
// 2. 如果找不到該行程,直接結束
if (exportData == null) {
// 處理找不到行程的情況,例如顯示一個提示訊息
return;
}
// 3. 將 Map 資料轉成 JSON 字串
String jsonString = jsonEncode(exportData);
// 4. 準備分享的檔案資料
final XFile fileToShare = XFile.fromData(
// 將 JSON 字串轉成二進位資料 (UTF-8 編碼)
utf8.encode(jsonString),
// 指定 MIME type 為 application/json
mimeType: 'application/json',
// 讓分享的檔案名稱固定為「{tripName}.json」
name: '$tripName.json',
);
// 5. 呼叫 share_plus 執行分享動作(跳出系統的分享視窗)
await Share.shareXFiles([fileToShare], text: '分享我的行程:$tripName');
}
這段程式碼首先會從資料庫中取得完整的行程資料,然後將它轉換成一個 JSON 格式的檔案。最後,透過 share_plus
的 shareXFiles
函式,呼叫系統的分享視窗,讓使用者可以選擇任何 App 來傳送這個檔案。
這個過程是匯出的逆向操作。需要讀取檔案,解析 JSON,最後將資料寫入資料庫。這裡最關鍵的挑戰是,不能使用舊有的 ID,因為資料庫會為新紀錄自動生成 ID,否則會發生衝突。
file_picker
選擇檔案要讓使用者能夠從手機中匯入 JSON 檔案,將使用 file_picker
套件。首先,在 pubspec.yaml
中加入該套件:
dependencies:
file_picker: ^10.3.2
實作檔案選擇與讀取功能:
/// 從使用者手機中選擇 JSON 檔案,並匯入行程資料庫
Future<void> importTripFromJsonFile() async {
// 1. 開啟檔案選擇器,只允許選擇 .json 檔
FilePickerResult? result = await FilePicker.platform.pickFiles(
type: FileType.custom, // 自訂檔案類型
allowedExtensions: ['json'], // 只允許 .json 檔案
);
// 2. 檢查使用者是否選擇了檔案
if (result != null) {
// 3. 取得使用者選中的檔案
File file = File(result.files.single.path!);
// 4. 讀取檔案內容為字串
String jsonContent = await file.readAsString();
// 5. 將字串解析成 Map 結構
Map<String, dynamic> jsonData = jsonDecode(jsonContent);
// 6. 呼叫資料庫 DAO 的匯入函式,把 JSON 資料存入資料庫
// 假設你已經實作 importTripFromJsonFile 方法
await db.tripsDao.importTripFromJsonFile(jsonData);
}
// 若 result 為 null,表示使用者取消選擇檔案,直接結束
}
這段程式碼會打開檔案選擇器,並篩選出副檔名為 .json
的檔案。使用者選取後,會讀取檔案內容並解析為 Dart 物件,準備進行後續的資料庫寫入操作。
匯入流程中最關鍵的一步,就是將整個行程(包含 Trip、Activities 與 ChildActivities)正確寫入資料庫。為了保證資料的一致性與完整性,使用 Drift 的 transaction 功能,將所有寫入操作包裹在同一個交易中。
這種設計既確保了資料的原子性,也保證父子關聯正確,對大量子活動的寫入仍能保持高效能,避免部分寫入失敗導致資料不完整。詳細實作可以參考前幾天寫的 Day 19 - Drift 實戰:從卡頓到流暢,打造高效能的批次寫入術 了解如何做批次處理。
匯出流程 | 匯入流程 |
---|---|
![]() |
![]() |