需求確認
Timer 的需求如下:
- Timer 時間上限為 60 分鐘,下限為 1 分鐘,預設為 1 分鐘。
- 使用者可以按下增加/減少時間按鈕,調整 Timer 的時間,一次調整以 1 分鐘為單位。
- 使用者可以按下開始鍵,開始倒數。
- 使用者可以按下暫停鍵,暫停倒數。
- 使用者可以按下停止鍵,停止倒數。
- 使用者可以從通知列得知目前的剩餘時間。
- 若倒數尚未結束,使用者離開 APP 之後必須要能夠繼續倒數
技術選擇
- 背景執行:
Service
- Timer 主體:
flow
+delay
- 畫面處理:
Compose UI
- 架構框架:
MVI
(Model-View-Intent)
針對這些技術選擇,我們來稍微解釋一下:
為什麼使用Service
從官方文件來看:
因為 Timer 需要能在使用者離開 APP 後能繼續倒數,所以我們需要一個能一直持續執行的角色,這時候Service
肯定是首選。
其他的選擇,像是
WorkerManager
,Thread
, … 等等,都可以去查一下差異,會更了解哪些時機點該使用哪些元件。
為什麼使用flow + delay
沒為什麼,因為想用。
用CountDownTimer
、Handler
+ postDelayed
也是可以。
為什麼使用 Compose
因為想畫的 UI 用Compose
來堆積會比較快,所以選Compose
寫了幾隻 Compose 範例之後,個人認為對比於傳統的xml來說,開發速度上真的快上滿多。
為什麼使用 MVI
因為Compose UI
現在官方主推的就是MVI
,就來試試看吧。
至於 MVVM 與 MVI 的差別,本篇暫不討論
實作與重構循環
確定使用哪些技術,開始實作之後,建議先畫個設計圖
你可能第一版會畫出這樣的東西:
然後你突然想到,不是說好要用MVI
嗎?那個ViewModel
怎麼不見了?於是
隔了一層 ViewModel 畫出了第二版:
一邊查資料一邊實作的過程,被你看到了MVVM
+ Repository
的概念,你決定修改
加上 Repo 有了第三版:
改到一半發現大家都在討論依賴反轉原則,你又動搖了。看了一下教學,覺得也沒有難改到哪邊去,決定一次到位給他改下去
加入抽象化的概念有了第四版:
心想這樣肯定沒問題了吧,加緊腳步的開發吧!
正當你快完成的時候,PM 發來一個會議通知:「Timer 需求變更討論會議」。
抱者忐忑不安的心進到會議室之後,PM 試探性的問了一句:「你的 Timer 應該可以在任何地方使用吧?我們現在很多地方要用這個 Timer!」
你開始覺得不太妙,因為你的 Timer 其實就是 Service 本人,你想了一想
把 Timer 獨立於 Service,畫出了第五版:
短短的幾天你已經改了五版了,實作重構的循環已經經歷了五次了,回到我的這篇文章的出發點上:
你怎麼知道要這樣寫?
因為出發點不一樣,有經驗的人起步可能就是無經驗者的第四個循環、第五個循環,當然做得又快又好。
所以經驗的累積,就是體現在自己下一次的起步點往後面的循環移動,至於怎麼做,就看每個人的學習記憶方法了,期許各位都能成為被問這句話的人,也能把自己的經驗分享,讓大家都一起成長!
最後附上這次範例的程式碼
範例程式中使用了startService()
跟 bindService()
。常有人不確定什麼時候要使用哪一種。
這兩者的最大的差異在於有沒有需要持有 Service 的實體,如有需要就使用 bindService(),反之則使用 startService()。
生命週期的部分,用 bindService() 拉起來的 Service,只要沒有 Client 與 Service 維持 bond 的關係就會自動消滅。若是由 startService() 叫起的Service,則需要搭配 stopService(),才能將 Service 停止。
混用的生命週期則可以自己插 log 做個實驗,也能參考範例中的 Log ,TAG 為FlowTimerService
那故事到這邊就結束了嗎?
並沒有。
還記得我們提過的回饋檢討嗎?
科技進步得很快,會有新的框架不斷問世,改善現有的問題,到時候就會有第六版、第七版。
想法也改變的很快,客戶需求變更隨時來,第八、九、十版也很常見。
既然改變無可避免,我們能做的,就是寫出當每次改變來臨都可以簡單應對的 code。
能夠很快應對改變、能夠很改很少的 code 就達到目標,這就是資深工程師的精華所在,也是價值所在。
大家一起加油吧💪