配合分支:day11/start,Repo: 按我
昨天注意到 App 一關閉,id 資料都會消失。這些 id 在 DiaryEntry 建立時才會被塞入 UUID。用現實一點來考量,每次儲存筆記後 id 都會變,那代表 id 不能保證就是代表某一則日記。
將 App 或是任何程式在電腦中跑起來,系統會把它們載入到記憶體 (RAM) 之中。隨著程式因生命週期到頭被系統移除(通常是關閉後被系統移除),這些記憶體裡頭的資料就會消失。必須用某一種方法儲存在硬碟或是其他的儲存空間之中(寫過網頁的話可以存在類似 Local Storage 的東西裡頭),才能將這些 id 永久保存成同一份,這招叫做 Local persistence。只要在本機有一份資料,就不用重新建立。
今天的修正,會將現在的日記儲存成 JSON 的格式,放在 Android 系統的內部儲存空間。JSON 格式的樣式長成這樣:
[
{
"id": "abc",
"title": "夜裡的共享空間",
"note": "雨夜裡留下來整理想法",
"mood": "專注",
"createdAt": "2026-04-04T21:04:51",
"photoResId": 123
}
]
為常用的網路傳輸時,使用的格式。讀者可能會注意到這個一個配一個的資料樣式,很適合 data class。沒錯,通常在實務上收到 JSON 資料後,會轉成 Data class 來存取操作。
"mood": "專注" 代表 mood 這個 key 配到 "專注" 這個文字嘿名稱裡有 Java,代表 Android 開發和 Java 的淵源難分難捨。
{} 大括弧裡頭,都視為一個物件,通常轉換為 Android 的 POJO。[] 裡頭的是 Array (陣列),也就是一個列表的物件。新建一個 Class,他的工作是在 Android 的儲存空間寫入 json 檔案。

參考上面的 JSON 格式,接著就來逐行講解這東西做了啥:
class LocalDiaryStorage(context: Context)
建構子需要一個 Context,這個東西來自於 Android,如果需要動到和 Android 系統有關的行為,像是寫入資料,就需要這個東西。
放心,Google 的 viewModel() 方法,會自動把這個東西給我們 (貼心 💐)。
private val diaryFile = File(context.filesDir, "diaries.json")
這行就是請系統建立一個 "diaries.json" 檔案。接著下面的 save() 方法,看到熟悉的格式:
val jsonArray = JSONArray() // 👈🏼 很多筆,所以包在 Array 裡,也就是 []
for (entry in entries) {
val jsonObject = JSONObject() // 👈🏼 建立一個物件 {},將 key-value 塞入
jsonObject.put("id", entry.id)
jsonObject.put("photoResId", entry.photoResId)
jsonObject.put("title", entry.title)
jsonObject.put("note", entry.note)
jsonObject.put("mood", entry.mood)
jsonObject.put("createdAt", entry.createdAt.toString())
jsonArray.put(jsonObject) // 👈🏼 把物件加到 Array 裡頭
}
diaryFile.writeText(jsonArray.toString()) // 👈🏼 最後寫入檔案
接著,在儲存日記時,要把結果寫入儲存空間之中。 但是 LocalDiaryStorage 需要 Context 怎麼辦? ViewModel 有個變體,叫 AndroidViewModel 可以取得 Application ,只要有這個,基本上就可以取得 Context。
所以先將 ViewModel 調整成 AndroidViewModel。
class PhotoDiaryViewModel(application: Application) : AndroidViewModel(application)
接著將 LocalDiaryStorage 黏在 ViewModel 上。
private val storage = LocalDiaryStorage(application.applicationContext)
寫入新日記時,順便寫入儲存空間之中:

除了這裡寫入以外,移除日記也需要移除相關的資訊。不過,現在日記還很少的情況之下,就直接重新寫一份回去:

寫入後怎麼驗證呢?Android Studio 可以藉由 Device Explorer 開啟裝置的內部結構。PhotoDiary 來說,位置在:
data/data/com.rayliu.myphotodiary/files/diaries.json
有些模擬器會在:
data/user/0/com.rayliu.myphotodiary/files/diaries.json

可以將這個檔案匯出到本機,就可以預覽他囉。例如,這是本次的預覽:

在介紹 JSON 時,提到數字的資料型態(Type),說到 Int 是常見但不全然是。就藉此利用機會來介紹其他的可能型態:
| 資料型態 (Type) | 記憶體大小 (Size) | 包含位元數 (Bits) | 數值範圍 (Range) | 適用情境 |
|---|---|---|---|---|
Byte |
1 Byte | 8 bits | -128 到 127 | 讀取檔案、二進位串流、串流媒體傳輸 |
Short |
2 Bytes | 16 bits | -32,768 到 32,767 | 大型陣列中節省記憶體(現已較少使用) |
Int |
4 Bytes | 32 bits | 約 -21.4 億 到 21.4 億 | 最常用的預整數型態,如迴圈計數、一般計算 |
Long |
8 Bytes | 64 bits | 約 -922 億億 到 922 億億 | 時間戳記、全域唯一識別碼 (UID)、大型資料庫 ID |
現在實務最常用的是 Int 和 Long,其他比較少用。如果要計算的數值,超過數值範圍,可能會發生溢位(arithmetic overflow)造成最後的結果錯誤。所以要計算天文數字,用 Long 可能會是一個比較好的選擇。
diaries.json 檔案。"mood": "專注",是 JSON 的基本組成方式。{} 與 []):JSON 裡 {} 是一個物件、[] 是陣列(一列表物件),對應程式裡的 JSONObject 和 JSONArray。LocalDiaryStorage 的建構子就靠它取得儲存位置。Application 進而拿到 Context,解決 ViewModel 裡無法建立 storage 的問題。