完成散步統計後,我希望把散步資料放到手機桌面上。
但 RN 的資料不能直接被原生 Widget 使用,因此這篇先處理資料怎麼從 RN 傳到 Widget。
React Native 的畫面由 JavaScript 執行,而 Widget 是原生元件:
因此 Widget 無法直接取得 RN 裡的資料。
原本可以讓 Widget 自己查 SQLite,但這會讓 iOS、Android 都需要重新處理資料查詢與統計邏輯。
所以我最後採用:
RN 負責整理資料,Widget 只負責顯示。
我建立了一份專門給 Widget 使用的 WidgetSnapshot。
它不是完整的散步紀錄,只保存 Widget 需要的統計資料:
type WidgetSnapshot = {
version: 1;
generatedAt: string;
userId: string | null;
today: {
distanceMeters: number;
durationSeconds: number;
walkCount: number;
lastWalkCompletedAt: string | null;
};
week: {
distanceMeters: number;
activeDays: number;
previousWeekDistanceMeters: number;
distanceChangePercentage: number | null;
dailyDistances: {
date: string;
distanceMeters: number;
}[];
};
};
RN 從 local_walk_history 取得正式散步紀錄,計算今日、本週、上週與最近七天的統計,再整理成 Snapshot。
Widget 收到後不需要查 SQLite,也不需要重新計算,直接使用 Snapshot 顯示。
Snapshot 加上 version,是為了處理 App 和 Widget 不一定會同時更新的情況。
例如未來新增:
Snapshot 格式就可能改變。
如果 Widget 還是舊版,就可能無法解析新的資料。
因此 Widget 讀取 Snapshot 時會先確認 version 是否支援,不支援就顯示空狀態,而不是因為格式不相容導致 Widget 出錯。
Widget 的資料來源只有 local_walk_history,也就是後端完成正式統計後保存的結果。
因此這些資料都不會直接進入 Widget:
因為散步中的距離只是前端根據目前 GPS 計算出的暫時結果,正式距離仍然要等後端收到完整資料後重新計算。
例如 App 目前顯示 2.3 公里,後端最後計算的正式結果可能是 2.2 公里。
因此 Widget 只顯示正式統計,避免 App 和 Widget 出現不同的結果。
RN 會先讀取 local_walk_history,再依照目前裝置的時區計算:
最後整理成 WidgetSnapshot。
日期區間與統計邏輯都在 RN 完成,Widget 不需要重新實作。
RN 產生 Snapshot 後,還需要把資料交給原生 Widget。
因此我把相關操作封裝成 Native Module,RN 只需要使用三個方法:
// 寫入最新的 Snapshot
updateWidgetSnapshot(snapshotJson);
// 通知 Widget 重新讀取資料
reloadWidgets();
// 清除 Widget 資料
clearWidgetSnapshot();
RN 不需要知道 iOS、Android 實際怎麼保存或讀取資料,平台差異全部由 Native Module 處理。
兩個平台使用不同的共享方式:
因此整體流程會變成:
local_walk_history
↓
React Native
↓
WidgetSnapshot
↓
Native Module
↓
iOS App Group / Android SharedPreferences
↓
Widget
RN 負責資料整理,Native Module 負責跨平台橋接,Widget 則只負責讀取與顯示。