此篇文章旨在分享我的歷程、並啟發其他科技公司選擇使用 Flutter。
開始的契機
這一切源自於一位過去與我工作過的專案主管 Peri。當時,他問我是否有可能在四個月內為 EntrenaPro 的 iOS 和 Android App 重做雛形。他分享了專案的 invision 連結給我。而且他對我們使用哪種跨平台套件持開放態度,只要可以幫助我們快速完成就可以。EntrenaPro 這個 App 為教練和運動員提供平台,以規劃多於 40 種西班牙盛行的運動個人或是公開運動課程,
Flutter 簡介
在 2015 年看過上面這段影片後,我非常喜歡它的概念,但一直沒有機會嘗試使用。直到遇上這個時間緊迫的專案,而且這個 App 需要建置於兩個平台上,我就趁著可以自由選擇套件的機會,選擇了 Flutter。自 2015 年到今天,Flutter 已經發展了一段時間。
為什麼我選擇了 Flutter,而不是 React Native
我完全沒有用過 Flutter,而 React Native 也只是用過一個星期(我嘗試過構建一個 App,但最後沒有完成),當要從兩者之中選擇時,我考慮過這些因素:
- 交付專案的速度、開發人員的產能,我在許多地方閱讀過相關議題。
- 當 Flutter 繪製畫面時,它使用自己的表層,然後將 Widgets(視圖、按鈕、文字)繪在表層上,所以 Flutter 可以控制視窗上的每個像素。Flutter 並非使用原生 OEM Widget 的;而 React Native 則是使用 OEM Widget 在畫面上繪製元件。這表示當你使用 Flutter 來繪製 Widgets 時,不論平台的原生 OEM Widget 是否支援,繪畫出來的 Widgets 在每個平台上看起來都會是一模一樣的。而且,Flutter 還具有平台 Widget 感測與平台物理感測。
- Flutter 使用 Dart 語言,它具有資料型別(之前是非必要的,但是從 2.0 版開始改為強資料型別)。我是從 Java/Swift 語言開始寫程式的,所以對我來說 Dart 比 Javascript 還要吸引。我讀了使用 TypeScript(我也愛這個語言)或是 Flow 的工程師所寫的文章,但是它比建於語言中的資料型別來的好,幫助了 IDE 了解程式碼,並讓 IDE 能夠提供程式碼補齊建議。
- 我偏心於 Google 的產品。
我不希望讓你覺的 React Native 就是不好。無庸置疑地,許多成功的 App 都是以 React Native 來開發,而且它們看起來棒極了。但是我對 React Native 的六天初接觸並不太好(試試著建立一個陰影參數為 x = 0、y = 2、blur = 6、spread = 0 的視圖、並在兩個平台上測試,你就會明白我的意思),所以我就試試這個新玩意。Flutter 與 React Native 以不同的方式提供跨平台開發,而我個人比較喜歡 Flutter。
Flutter 的旅程
安裝
安裝過程對我來說十分簡單。如果是 Android 開發者,你應該已經安裝了 Android Studio。
- 選擇 Clone Flutter repo
- 設定環境參數
- 執行
flutter doctor
指令 - 在 Android Studio 裡安裝 Flutter Plugin
就是這麼簡單!如果還不清楚安裝步驟,你可以看看官方指南。雖然我從未試過在安裝時遇到困難,但是亦看過其他人在安裝時會碰上一些難題,在這個情況下,它的社群就非常有幫助了。
上手 Dart 和 Flutter
我過去是撰寫 Java 語言的,所以在我第一天開始撰寫基本的 Dart 時,發現它們的語法十分相似;而繼續撰寫後,我的語法亦開始不斷進步。Dart 內建支援 Streams 及 Future。我的 RxJava 經驗有助我了解 Streams,而在 ES6 Promise 方面的經驗則有助我理解 Futures 和 Async/Await 的概念。我的工作效率從第一天開始就非常高。我認為有強型別語言經驗的開發者會很快上手,而擁有動態語言經驗的開發者就可能會遇到一點困難。
在 Flutter,所有東西都是 Widget;這與我舊有的經驗非常不同,但是不同的文件和教學幫了我很多。我在頭兩三天感到非常困惑,因為有太多新的概念,佈局 (layouting)、樣式 (styling)、內距 (padding) 等所有東西都是一個 Widget。但了解 Android UI 系統與 Flutter UI 系統間的概念後,事情就開始變得簡單了,我亦開始更有自信。而且我非常喜歡當中的宣告式 UI (Declarative UI)。
Flutter Hot Reload 和 Hot Restart 功能的概念就是減少測試時間,每次 UI 變動只需花上 0.7 秒(多數時間)至 2 秒(有時)來在專案畫面上顯示。加上,Flutter 也保留 Widget 的狀態。假設你輸入了文字並勾選了幾個 CheckBox,當你改變 CheckBox 的顏色時,只有顏色會被更改, CheckBox 會繼續保持勾選狀態;這樣提高了我的工作效率。如果要去計算的話,我認為它與原生開發測試相比節省了超過 40% 的時間。
就如其他的程式語言一樣,Dart 也有套件管理解決方案,他們稱之為 Pub。由 Dart 官方支援,與 NPM 十分相似。
第一週、第一個畫面、適應性、重用性
在我開始的第一週,我完成了登入和註冊畫面,且成果非凡,適應性非常棒。當我傳送裝置畫面截圖給設計師時,他也認同畫面非常和諧。
Flutter 使用宣告式 UI,大大增加了 Widget 的重用性。同時,我建立了一個 “Bonus” Widget,並且在多個地方使用它,而 “User Avatar” 也是如此。將任何東西提取到 Widget 都十分容易,而且這個 Widget 還可以在任何地方重用。
享受挑戰
在 Flutter,你不會「面對」挑戰,但你會「享受」它們。
舉個例子,根據畫面的設計,我需要一個月份選擇器,但卻沒有可用的套件。PageView Widget 是最接近可以組成的 UI。我深入理解程式碼架構後,創建了自己的 Widget,並發佈到 Pub。
然後在另一個畫面,我需要一個雙邊範圍滑桿 (range slider),但它只有單邊的 Slider 可用。我又探索了 Cupertino Slider 的程式碼,然後創建一個新的雙邊範圍滑桿 Widget,並發佈到 Pub。
整合 Stripe
這可說是項挑戰,因為 Stripe 在 Dart 內並沒有官方支援 。在 Pub 上是有幾個可用的套件,但是我們需要更細緻的整合,所以我決定為此寫個 Plugin(一種 Dart 套件,包含了以 Dart 撰寫的 API,當中整合了特定平台的實作像是 Android 的 Java 或 Kotlin、和 iOS 的 Objective-C 或 Swift)。但是當我在 Github 上檢查 iOS 與 Android 的 Stripe SDK 時,我發現它們的程式碼並不相似。在 Android SDK,它們一個函式會回傳結果,但相對在 iOS SDK,函式卻不會回傳結果。所以我打消了這個念頭,並開始嘗試另一個方法:閱讀它們的文件,並以文件和 Android SDK 為參考,來完成 App 所需要的整套功能。
推播通知
我在 iOS 與 Android 上都使用了 Firebase Cloud Messing。Flutter 上有官方插件可以使用,所以我首先使用該插件來整合推播功能。但是後來的有些需求改變了,而且我們需要在螢幕上依據推播通知的資料作即時內容更新,所以我們必須在專案中加入 Platform Channels。我移除了官方插件後,開始為撰寫 Android (Java) 與 iOS (Swift) 自己的 Firebase 整合程式碼。使用 Platform Channels,無論何時在平台端收到推播,我都可以推送資料到 Dart。這樣一來,當使用者點擊推播時,我就可以依據推播型別開啟特定的視窗。
程式碼架構
在大型專案裡,程式碼的架構非常重要。開始一個專案時有幾個熱門選擇:1. Redux、2. Scoped Model。雖然我一開始是採用 Redux 架構的,但我個人不太喜歡 Redux,我不太喜歡將所有東西全部存放在一個地方。幸運的是,在 Google I/O 2018 中,他們有談及到這部分,然後我就將架構轉換為 BLOC 模式,它滿足了我所有的需求,我現在仍在學習 BLOC 模式的最佳實踐。
翻譯
期限逼近,此時專案主管認為我們需要多增加一位開發者,讓他可以同時負責翻譯工作。在開發期間,大概因為我還是個新手,所以犯了個錯誤,沒有從 App 的在地化檔案中將字串提取出來,所以要由其他開發者負責將字串提取出來並完成翻譯檔案。這花了他一個星期時間來完成翻譯。
之後專案主管要求我更改 App 裡的一些翻譯,好讓它的意思更具體。因此我才了解與 Android 相比,在 Flutter 裡翻譯是多麼的麻煩。我們用了 Dart 的 intl 工具來產生 .arg 檔案,然後在轉回 .dart 檔案。
建置版本與發佈
建置版本非常簡單,在建置一個釋出版本時,官方文件幫了我很大的忙。如果你對在 Xcode 及 Android 上建置版本有信心的話,那麼你會覺得得心應手。要產生 Dart 執行程式碼,我們只需要輸入幾個額外指令。在 Xcode 中,我們只需要照著一般程序來建置/打包/上傳到 App Store;而在 Android 中,按下幾個鍵盤按鍵就可以產生一個釋出建置。
App Store 只需要一天就完成審核,並上架到商店。現在,App 在 App Store 和 Andriod 都可以下載了。
我的下一步是?
現在,我正專注在 Animations/Hero Animation 和客製 Widget 上。我必須深入探索程式碼裡的說明,並學習一些進階的東西。Isolates 是我下一個題目,我們必須在背景執行緒中執行一些沈重的工作。整個時間表可說是非常緊湊,且我們需要盡快完成 App,所以我們無法專注於測試方面。接下來,我們就要了解 Flutter 裡的測試。最後,我也必須學習持續整合 (Continuous Integration) 與持續發佈 (Continuous Delivery)。
總結
我認為 Flutter 是商業 App 的最佳選擇,它幫助我在緊迫的時間內完成了第一版的 App。它可以讓 App 的 UI 在 iOS 看起來非常漂亮,而且在 Andriod 上亦會維持同一狀態,這一點為我帶來了非常大的滿足感。如果你需要取用特定平台上的功能,Flutter 也有提供機制來輕鬆處理。Dart 語言很容易學習;Flutter 的 HotReload 功能也為我節省了大量時間,並提升了我的生產力;它的社群也正蓬勃發展;再加上,Flutter 的工具及 IDE 支援更好。
綜覽 App 功能
在讀過這篇很長的文章後,如果你還想知道使用 Flutter 建置的這個 App 有甚麼功能的話,讓我簡單說明一下 EntreaPro App 目前提供的功能:
- 兩個不同角色(運動員與教練)有不同的選項、付款細節、側邊欄和功能。
- 定位搜尋教練與運動員(使用 Place Suggest API)。
- 已預定體育課程的總覽,當中分類為待決、已接受、和取消。
- Stripe Connect (使用 WebView 來處理重新導向)讓每位運動員可以使用信用卡付課程費用予教練
- Stripe(使用建置在 Dart 的客製 API),讓教練可以付費訂閱 EntrenaPro。
- 體育課程折扣,由教練販售,其中有一部分已經準備好在課程後使用。
- 新課程請求、接受、取消、付款等的實時更新與通知(使用 Firebase Push Notification)。
- 日曆選擇,以為各個專業項目篩選可選時間。
- 評價系統,提供星級評比、評論、和回應。
- 教練筆記本,讓他能夠持續追蹤他提供的課程和運動員表現。
- 教練個人資料共享(使用 Firebase Dynamic Links)。