MF99 coding 💻

keep learning; keep coding;

ME-App:我的履歷 App

終於能來寫這篇了~

最近在工作轉換期的空擋。之前自己一直很想寫一些東西,一來當作個人作品,二來也可以練習一些之前一直想玩,但是工作上沒用到的一些東西。

所以最近剛好有時間了,就來安排一下~

Play Store 已上架完成

play.google.com

要做一個 App,首先就要有個方向。 主要有兩個思考方向:

  1. 想要呈現什麼內容?
  2. 想用什麼技術元件?

內容部分,其實之前有過很多想法,工具類、遊戲類、社群類。 但是由於沒有太多資源可以利用,最後決定就做一個個人履歷的內容。

計劃階段

簡單明瞭,一來從技術面可以練習一些技術框架跟工具,二來從內容面也可以比較有彈性的呈現一般履歷無法呈現的效果。 而且內容都是自己的經歷,所以內容產出與整理都可以自己完成。

確定方向之後,再來就是 Brainstorming 來確認一下大概這個 app 的 scope 要多大

f:id:mouseface99:20200605102643j:plain

大概就是分成四大區塊:

  • 工作經歷
  • 做過的專案
  • 擁有的技能
  • 其他個人作品(Blog 文章)

然後工作經歷,專案與技能之間其實都互有關連,加上之前就想玩玩看 Room,於是這樣剛好就可以練習一下設計 SQLite 的關聯資料表。

設計階段

大概有個方向與具體內容之後,接下來就是進行初步的設計階段,這部分也分成幾大塊

App 介面設計

首先要先知道,User 用起來大概會是什麼樣子,要怎麼呈現 Mind map 上面的內容。 最後決定使用常見的底下有個 TabBar 然後用 List-Detail view 的架構

f:id:mouseface99:20200605101129p:plain

App 架構設計

致知道 UI 要如何呈現後,就可以開始規劃 App 的架構了。 參考之前 MVVM 的設計模板 ,把頁面,ViewModel 跟資料的部分進行規劃與安排

f:id:mouseface99:20200605101542p:plain

這部分的設計思維是:

資料的部分,一來為了方便更新,二來為了練習,所以決定是從 Server 端即時更新。

首先利用一個 Config file 確認資料與結構的版本,如果確認本地的數據落後之後才需要更新。資料從 Server 下載後整理好並更新本地的 Database,之後所有的頁面就可以直接透過本地的 Database 存取資料。

Data Model 的部分,一樣跟之前提到的一樣,我會開一個 App 內部使用獨立的 App Entity,用來與 JSON parser POJO 與 Room Entity 區隔,然後寫一個 Adapter 工具來做資料格式的轉換。 這樣就能確保 UI / Server data / Database data 格式之間不會有太高的耦合度。

這樣就同時包含了 Retrofit/JSON parser 與 Room 的練習。

Server 數據這一塊,由於沒時間去研究 API 與 Backend 的撰寫,所以就直接把產好的 JSON file 直接放在 Firebase storage or AWS S3,然後透過 public link 直接當成 HTTP GET 的 path,這樣就可以模擬出 API 的效果。 數據要更新直接更新雲端檔案就好(不過要注意版本與 local cache 的問題)
後來因為 Firebase 還有提供其他的分析與測試功能,所以後來選擇使用 Firebase Storage

要用到 Database 之前,首先要來進行資料庫的設計。 這部分首先我是先在 Google Spreadsheet/Excel 上面整理好工作、專案、技能的資料

大概像這樣:

f:id:mouseface99:20200605102159p:plain

綜合考量資料屬性以及之後 UI 的呈現方式後,就可以大概知道所有的資料欄位,以及有哪些欄位是在 Table 之間需要關聯的。 哪些欄位是要放 reference ID,哪些欄位直接放 value 就可以。

像我原本設計 Company 跟 Position 是獨立的 Table,然後透過 Reference 的方式關聯公司,職務與專案。 後來發現公司與職務其實是 1-1 的關係,而且其他地方其實也用不到 Company ID,所以之後就改成直接把 Display name/image 放在職務(Job)的 table 裡面,然後專案的部分透過 job_id 關聯就可以。

之後就可以產出一份簡單的 DB Schema

f:id:mouseface99:20200605102509p:plain

實作階段

既然介面/技術架構設計都有了,接下來就是動手實作了~ 這部分其實也沒什麼好寫,就是按照規劃的架構與設計一步步完成。

具體的程式碼與 Git history 可以參考這個 app 的專案

gitlab.com

目前做出來的效果大概如下

f:id:mouseface99:20200610003048p:plain

大致的 UI 架構與 Mockup 類似,不過做了一些微調。 並且加了一個 Splash screen,一來練習一下 Animation,二來也把 remote 的資料檢查,下載,處理在前期完成,這樣進入 app 後就只要存取 local database 就好(也可以當作離線版使用)

測試與微調階段

初步完成之後,其實就是花點時間測試跟介面上的微調,目前還在研究 Espresso 與 Firebase 的 Test Lab 服務。

目前是有計畫會把這個 App 上到 Google PlayStore,並正式當成自己的作品,不過現在 PlayStore 上架流程變得更複雜了... 跟之前 D3 App 的時候比起來更麻煩,而且有些步驟還要等 Google 審查,所以等上架完成後,再把連結放過來。

技術/技巧/工具整理

這邊就梳理一下這個 App 裡面有使用到的技術/工具

App 架構

  • MVVM
  • AndroidViewModel
  • LiveData

UI 流程控制

  • JetPack Navigation (with SafeArgs)
  • ConstraintLayout
  • CollapsingToolbarLayout

遠端資料與 App 數據分析

  • Firebase Storage
  • Firebase Crashlytics
  • Firebase Analytics

Restful API

  • Retrofit
  • Moshi

本地端 Database

圖片下載/Cache

  • Glide

測試 (研究中)

  • JUnit
  • Espresso
  • Firebase Test Lab

後記

其實這個計畫,一開始真的只是想當作練習與留個還能看的作品(之前的 D3 App 架構根本不能看...)

不過在整個實作過程,其實還是蠻有趣的。 尤其在資料收集時,首先要努力回想過去做過的專案,而且還要盡可能想辦法在網路上找到相關的圖片照片(有些早期的真的很難找.....),還要努力的回想當時專案的內容細節,以及盡可能地表達「我」實際在專案內負責的內容。

在技術上,的確這個 app 某方面也是要展示一些自己的技術能力,所以除了原本就熟悉的東西外,也想要練習一些新的工具與技巧,這階段也發現了很多以前不知道的一些 Android 上面的進化。 這邊就簡單提兩點:

String plurals

過去我們在畫面上要顯示一些文字的時候,可能有些情況會需要把本地文字與 Server 的數據結合後才呈現。

比方說在我這個專案內,我有個欄位要顯示某個技能在多少個專案內使用到。 所以我在 string.xml 裡面就需要加上一個

...
<string name="projects_for_skill"> in %d projects </string>
...

然後在確認數字之後,在 code 裡面使用

val numOfProjects = data.numOfProjects
textview.text = context.getString(R.string.projects_for_skill, numOfProjects)

但是這時候通常會有個問題,就是在英語或是某些歐洲語言中,單數與多數在呈現上會有一些差異(像是英文中複數就需要在結尾加上 s)

過去需要處理這中狀況,通常都會在 code 裡面在 getString() 先去做一些資料檢查,然後根據數字形態的不同而去取不同的 R.string.<id>

但是這次發現了其實 Android 現在有提供了這種功能:String plurals

用法上類似,就在 string.xml 裡面更新一下

...
<plurals name="projects_for_skill">
        <item quantity="one"> in a project </item>
        <item quantity="other"> in %d projects </item>
</plurals>
...

quantity官網上有多種選項可以使用,不過以英語來說大概就是單數與複數,然後 Android 系統會根據不同 quantity 的定義取用不同的字串版本(有點像 <selector> 的用法)

然後在 Code 裡面更新一下使用方式

val numOfProjects = data.numOfProjects
val res = context.resources
textview.text = res.getQuantityString(R.plurals.projects_for_skill, numOfProjects, numOfProjects)

這邊有看到一個特別的點是,numOfProjects 需要丟兩次,按照官方文件的說明,第一個數字丟進去是為了讓 plurals 系統選擇目標字串,第二個數字才是用來做 string format (%d) 的用途。

所以在某些情況,比方說你的 1 想要顯示 one,那就第一個數字丟 1,第二個丟 'one' 字串,或是你的顯示文字裡面不用顯示到數字,那就丟第一個數字就好

另外要注意一點! 這個功能只在對應的語系中才會有作用!像是我放了這個 plurals 的功能,如果手機的設置是在中文語系的話,就不會有作用(就都顯示 other 的 case)

Animations and Transitions

第二個就是 Animation 系統,雖然我在 HTC 的時候也簡單玩過一陣子最早期的 View animation,但是那時候的版本還很難用,而且效率很差。 並且會跟 UI 複雜的操作互相干擾與影響,所以後來實務上的專案也很少使用(也要看 UI/UX 有沒有設計,或是 Dev team 有沒有時間做),頂多是在 Activity / Fragment 畫面切換的時候做一些 Window transition 動畫。

不過這次在研究中,發現 Android 現在的 Animation 系統變得非常強大而已易用,不管是畫面(Activity / Fragment)之間轉換的動畫, View 的元件互動與連動的動畫也相當強大,而且新增的 Animator/AnimatorSet 與衍伸出來的 ValueAnimator / ObjectAnimator 讓整個動畫變得更好操作,以及整體效能是有完整的系統支援(而不是單純像以前只是開一個 Thread 然後不管其他人的播放而已)

這部分我在這個專案裡面還沒有用到很深,只有在 LauncherActivity 裡面玩了一下 ViewPropertyAnimator,原本想要在 Job/Skill 的 List/Detail page 玩一下 shared element transition,但是試了幾次效果不好,所以後來先拿掉了。

關於目前 Animation 的系統與內容,可以參考Android Dev Summit '18 的這個影片,蠻推薦 UI/UX designer 也可以看一下~ 除了 Material design 之外,還可以看一下 Android 裡面的元件可以利用原生的機制,做到怎麼樣的互動與效果!

www.youtube.com

Reference

最後也提供一些相關連結