
想留住自己做過的美甲照片,翻遍市面上的美甲 App,幾乎每一款都要先註冊帳號,把照片上傳到別人的伺服器才能用。這個 App 刻意做得很小,真正的目的是把上架 App Store 這條路完整走一次,把每一格表單、每一項合規申報親手填過一遍。
把照片留在手機裡,才不用經過任何人同意
這個 App 是一個給自己用的美甲筆記與照片相簿,功能單純:拍照、記錄、收藏。它完全不連雲端,也沒有帳號系統,照片和筆記全部留在手機本機的儲存空間裡。換來的是零後端、零隱私外洩風險,而且沒有網路也能正常使用。
技術細節:本機資料層與技術棧
- 框架:React Native 0.81 + Expo SDK 54 + Expo Router,TypeScript strict
- 資料庫:expo-sqlite(SQLite,WAL mode,同步 API)
- 照片:expo-image-picker 選圖後透過 expo-file-system 複製到本機 documentDirectory,不依賴外部儲存服務
- UI:expo-glass-effect、linear-gradient、reanimated(處理 pinch 手勢調整相簿欄數)
- 刻意不接:Supabase、Auth、任何後端、Sentry
敢砍掉複雜的資料結構,上架速度自然就快
我另外做過一款雲端社群版的美甲 App,Nailora 美甲情報 App 複盤,那個版本一張照片要對應多種尺寸的網址與模糊預覽圖,因為要餵給雲端的內容分發網路。這個離線版讓照片直接對應一個本機檔案位置就夠了。少了這層複雜度,從空白畫面走到把 App 送出審查,速度快得明顯。
資料庫要能自己修好自己
開發時反覆重新整理畫面來看效果,這個動作有時候會讓資料庫的升級程序卡在做到一半的狀態。只靠一個「版本號跑到第幾關」判斷有沒有升級完成,卡住之後它會誤以為自己已經做完了,之後就一直修不好。改成每次啟動都直接檢查「這個欄位到底存不存在」,不管資料庫當下卡在什麼狀態,都能自己補完該做的事。
技術細節:Migration 自癒機制
- 用
PRAGMA table_info()逐一判斷欄位是否存在,而不是只靠user_version這個版本號判斷- 問題根源:DB 單例在 Fast Refresh 時會被保留,導致 migration 不重跑,卡在半遷移狀態
- 解法:本地開發遇到這種狀況,需要完全關閉 App 重開才會乾淨
- 這個判定方式讓資料庫在任何中斷狀態下重啟都能自我修復,不需要手動清資料
功能合併之後,資料表刻意留著當安全網
相簿功能已經併進筆記的顯示模式,介面上少了一個入口,底層的資料表刻意沒刪:留著沒有壞處,將來要把功能拆回來,資料不會憑空消失。App 每次啟動也會掃照片資料夾,把沒有任何筆記指向的檔案清掉,避免手機儲存空間被用不到的檔案慢慢占滿。
不連網讓隱私問卷變得很簡單
上架 App Store 要填一份問卷,交代這個 App 到底拿走使用者的什麼資訊。這個 App 完全不連網,也不會把任何資料傳出手機,問卷可以直接勾選「未收集資料」,加密傳輸那一項也完全不用申報。
技術細節:上架申報與送審流程
- 隱私問卷勾選「Data Not Collected」
ITSAppUsesNonExemptEncryption: false,免除出口加密申報- 流程順序:CRUD 與照片本地化 → 相簿合併與排序 → 正式 icon/splash + 隱私頁 → tsc 全綠 → EAS production build → submit
現在的狀態是等蘋果審核,產品本身其實很陽春
這個 App 已經完成並送出 App Store 審查,通過之後還要手動按下發布才會正式上架。走完這一趟,知道每一關實際上會被問什麼、卡在哪裡。
敢砍資料結構,才是這次帶走的東西
離線優先從源頭就把金鑰外洩、資料庫權限設錯這類風險整個砍掉,因為沒有後端,就沒有這些東西需要顧。從一個大專案拆出一個小工具的時候,最有價值的是敢把母專案那套為了雲端而設計的複雜資料結構,簡化成本地夠用就好的最小型態。
相關
專案複盤-MOC Nailora 美甲情報 App 複盤 — 本專案的母體,雲端社群版