2017年8月25日 星期五

前端框架 Angular Dart 小小心得

前言

本文分享我在 Angular Dart 開發一年來的經驗,重點放在一些議題的思考上,並不是講解特定技術的文章。

因為工作關係開發方向由行動轉到Web前端領域,早聞前端的世界變化萬千,進門後也深有同感,既然變化如此快速,就不要提太久遠的技術了。

近一兩年呼聲較高的前端「技術」不外乎 Angular, React, Vue.js 與其他 *.js 眾們(太多啦,原諒我沒一一列出)。

縱使這些「技術」們各自專精的部份不同,但他們幾乎都能幫你實現所有需要的功能,差異在於實現的過程、成本與適切的程度。

本文主旨「前端大混戰之 Angular Dart」,對於 Angular 尚未接觸的讀者,或剛接觸即擁抱 TypeScript 的開發者也許會對 Dart 有些陌生。現在的 Angular 揮別了 Angular JS 時代--幾乎是一種「學習曲線高」的代名詞,在 2016 年 Angular 2 釋出時,它走出了三條路線,或說以三種語言來實作這個前端框架,分別是 JavaScript, TypeScript 與 Dart。

面對現實吧

你想從這些既有的框架中得到什麼呢?

技術狂熱者,可能會想要把所有新鮮技術都試過,更甚於深入其底層探就一番;也許你有個不錯的想法,想要快速實踐;也許你想要學習新技術,作為日後工作的籌碼;也許你是學生,需要實作個系統好完成報告。

以上,如果你是基於這些理由想要挑選(學習)前端框架(或相關技術)我想都不是大問題,但若你是要打造企業產品,絕對需要多想幾回。通常一項資訊產品背後往往藏有相當可觀的成本,產品很難是拋棄式、用完即丟的。

我的看法是,引入新技術除了確認它是否可以解決我們現有的問題,同時也要避免引入後的「新問題」。例如開發團隊是否容易上手嗎?需要多久時間來建構開發標準與典範,特別是在這個技術很新的情況下!以及團隊的反彈,為什麼要學新技術?特別是這門技術好像不會用很久,似乎隨時會被取代

Dart vs. Type Script

撰文的當下(2017/08),不諱言 TypeScript 的社群之大,並非 Dart 可及。其作為 JavaScript 的超集,先天上就帶有一些優勢。

Dart 作為一門年輕的語言,呼聲相對小了些。即便最終部署時仍需轉成 JavaScript (大概是Google也很難要求各家瀏覽器嵌入Dart VM 吧),但它在開發階段確實帶給前端開發者一種新的、丟掉歷史包袱的開發體驗。

Java Script 的歷史包袱以及各種弔詭事蹟就不再贅述,各位也可以從某個動物系列的資訊書商看到各種 JavaScript 的「解疑」書籍,不難想像其骨子裡藏有多少陷阱。

Dart 可以為開發團隊屏蔽一些盲點,這是我希望達到的效益,其本身也有不差的生態系,自己的 Package 管理工具 (Pub),單論開發 Web App 是足夠,嗯…?這樣還是不能說服你嗎?Google 自己也在用,這邊有一份清單可供參考 Who Uses Dart

Angular Dart 帶來的優勢

易於學習

也許子標題下得略顯聳動,但實際執行下來 Angular Dart 的學習並不是特別困難。經過幾輪的教育訓練,開發團隊是具備開發與維護的能力,且這些成員事前並不具備相關框架的使用經驗。

Angular 這個框架包山包海,大多數需要實現的功能它都有對應的解決方案,不像一些技術是發散的,要實現單一功能,有太多不同的實作來解決,反而舉棋不定。

Angular Dart 的挑戰

開發工具支援不足

現階段支援度最高的 IDE 是 WebStorm (需付費),其次是 IntelliJ (有免費版),有別於 TypeScript 通常基於 VS Code (免費) 進行開發。在 Plug-in 方面,Angular TS (for VS Code) 的資源亦是相當豐富,Angular Dart 目前還是掛蛋的狀態 (有沒有善心人士要投入開發的 😆)。

測試

這邊指的測試是單元測試,基於 test 與 angular_test 兩個 package 進行。執行的成本不低,其需先經過 Dart to JavaScript 的過程,對於硬體的需求較高。即便其標榜轉譯後即會快取,且是漸層式的,但仍然避不掉「第一次」執行時耗時的問題。

我們也嘗試將測試執行在 Chromium 之上,令其跳過轉譯過程,這招的確解決了耗時問題,但有時仍會遇到一些未曾見過的問題,再加上 Dart 2.0 之後不使用 Chromium,這可能不是最佳解。


我也有試過 Angular TS 的測試,感覺體驗比 Dart 版的好上許多。

SSR 支援

Angular Dart 目前缺乏 Server Side Render 的支援,不像 Angular TS 可以使用 Angular Universal,在某些情況下不利 SEO。

文件

官方文件不及 TypeScript 齊全,有時候開發得仰賴通靈或是拿出科學家作實驗的精神突破難關。


總要有點希望

至今 Dart 與 Angular Dart 仍是持續成長,文件也有陸續增補,社群雖然不大但還有前進的動力。撰文的當下 (2017/08),Dart 2.0 與 Angular Dart 4.0 即將釋出,期待這微妙的組合能夠再帶給前端開發者全新的體驗。




2017年2月1日 星期三

將 Angular2 Dart 專案部署至 Firebase

前言

本文介紹如何將Angular2 Dart專案部署至Firebase,這是個十分有趣的應用方式,諸如Angular這種前端框架可幫助我們將特有的系統運作邏輯於前端實現,再配合Firebase提供的Database、Storage與Authentication作為後端,可使我們輕鬆建構並部署Web AP。

本文並無探討Angular2 Dart的設計與建構,僅關注在如何將其部署至Firebase Hosting。

事前準備

實際上在 Firebase Console 中的 Hosting 即有簡明引導教學,但有些細節仍需要注意。

請參考以下步驟進行:
  1. 在 Firebase console 中建立 Firebase 專案
  2. 安裝 Node.js (以便取得npm)
  3. 安裝Firebase CLI (npm install -g firebase-tools) 
  4. 登入Google (firebase login)
  5. 啟動(初始化) Firebase專案,請見下一小節

Firebase 專案初始化

現有Angular2 Dart專案若要部署至Firebase即需將其初始化為Firebase專案。具體操作為:
  1. 切換至專案根目錄。
  2. 執行 firebase init 指令。
執行firebase init指令時,會出現互動式的命令列操作,流程如下:
  1. Are you ready to proceed? 回答 Y
  2. What Firebase CLI features do you want to setup for this folder?
    •  Database: Deploy Firebase Realtime Database Rules
    •  Hosting: Configure and deploy Firebase Hosting sites 選擇此項
  3. What Firebase project do you want to associate as default?
    將會列出您目前在Firebase Console內已建立的專案,也可以在此新建專案。請選擇您要關聯的專案,即您要將專案部署到哪個Firebase專案。
  4. What file should be used for Database Rules? 採用預設值 database.rules.json
  5. What do you want to use as your public directory?
    預設為public,我們需要調整它,將它指向build目錄,這部份您可以參考下一小節「調整設定檔」,若當下您尚無法確定,也可以事後更改此項。
  6. Configure as a single-page app (rewrite all urls to /index.html)? 回答 Y
  7.  若一切正常您將看到 Firebase initialization complete!

確認與調整設定檔

當您完成firebase init操作後,在你執行該指令的目錄下將產生兩份重要的設定檔 ── firebase.json與database.urles.json。

讓我們先來看看firebase.json,它的內容大致上是:
{
  "database": {
    "rules": "database.rules.json"
  },
  "hosting": {
    "public": "public",
    "rewrites": [
      {
        "source": "**",
        "destination": "/index.html"
      }
    ]
  }
}
請特別注意 hosting 節點下 public 值,我們必須把它指定到 build 目錄下。

這是因為 Angular2 Dart 應用需要將 Dart 轉譯為 Javascript,以及將零零總總的 package 資源彙整,這些操作是透過 pub build 來完成,預設所有產出的資源檔案皆置於 build 目錄中。

而database.urles.json記載了Firebase Database的存取權限設定,這份設定會覆蓋您手動透過Firebase Console中進行的Database設定,不可不慎!

執行部署

使用指令 firebase deploy 即可進行部署。
此指令會將定義於firebase.json → hosting → public 所指目錄中的所有資源上載至firebase。

2016年10月25日 星期二

寫心經APP開發心得





寫心經是我第一款獨立開發並上架至Google Play的作品,歷經多次改版,也收到不少使用者的鼓勵與建議,今天來跟大家分享開發過程與心得!

 緣起

「寫心經」是在自我實現的期望下誕生的,當時想利用所學做出實績,經簡單的分析發現在生活美學相關的App仍不多,經過一些規劃與思考,逐步將其完成。

在決定開發這款App的當下,智慧型手機與App的生態環境已略為成熟,各界興起開發App的風氣。作為App發行重要平台的「Google Play」給了許多開發者發展機會,大幅降低了進入門檻。


從何開始

寫心經App是如何誕生的呢?或說從無到有,要創造一款App需要做什麼?
 

  1. 題材發想
    要做什麼? 在行動裝置上書寫經文
    要有什麼功能(概略)? 書寫、比對、儲存…
  2. 確認市場方向
    App的分類是什麼? 生活品味
    這個分類的現如何? (當時)不多見
    這個題材是否有人需要?
  3. 開發技術評估
    要兼顧些平台的使用者?
    要採用跨平台的開發技術嗎?
  4. 系統原型設計
    視覺化的東西有利進一步分析
    試著做出原型與視覺設計
  5. 系統開發
    實際撰寫程式,使其滿足當初規劃
    釋出前的品質確認,可以透過一些測試方法來評估
  6. 上架作業
    備妥文案與圖資,至各應用程式平台進行上架
  7. 更新維護
    定期閱覽評價與評論
    隨時追蹤平台的規章變更 (通常會發信通知)
    隨時追蹤第三方資源的安全更新 (通常會發信通知)

上述的每個步驟若再展開,仍有說不完的細節。由於資訊技術與環境的快速變動,每個時期會經歷的技術都不盡相同,在這邊不多談太多細節。

獨立開發意謂著所有事情都要自己來──不嫌累,學更多!


聆聽使用者

任何一款App都需要聆聽使用者的需求,但不只是在開發前聆聽一次,你必須不斷蒐集回饋。

寫心經有一些功能即是來自使用者的建議,有時你得到的建議正好與你自己想做的相符,但仍需考量實現的困難度。近期比較重大的改革是加入字跡記錄的功能,它的概念很簡單,即是儲存使用者書寫的字跡,並彙整輸出成手稿檔案。說來簡單,但要實踐它仍需費一番功夫,例如:如何儲存字跡?用什麼格式來存?如何縮放筆跡?如何排版為手稿?有許多問題需要開發者一一克服。



手稿輸出



無處不在的挑戰

App猶如任何一款資訊產品,都需要思考營運問題,即便沒有「營利」行為,也要為了滿足使用者努力,定期維護與發布更新版本的動作是少不了的。這些過程是充滿挑戰的,挑戰可能來自Android版本的演進,老舊的程式需要翻新、碎片化的硬體規格,讓你不易兼顧所有使用者與特定功能的實踐困難等。

面對這些挑戰,開發者必須有隨時跟上技術演進的準備,實際上這也是資訊人的必備技能,你必須不斷學習,但學習的成本是高的,無論是金錢或時間,因此你還需要培養強大的自學能力作為後盾 (關於自學的經驗,再找機會與大家分享)。

若你的APP具營利性質,挑戰則不僅功能維護,更包含如何培養、發掘、維持客源的問題。


成果

至今寫心經已累計萬次安裝,我在開發過程中學習不少,這也為我的工作打下基礎。成果可以量化,也可以是模糊的,總之這些經驗的累積多少會令你成長,無論是實質上的技術,或是思考問題的角度。

也要感謝每位使用者的迴響,您的意見、評價與評論都非常寶貴,讓我能夠針對應用進行調整與優化,期望寫心經能帶給您愉快的體驗。


後記

資訊界總是一波又一波的新趨勢,本文撰寫時App已不是時下焦點,取代而之的是VR/AR, IoT, FinTech。在競爭激烈的背景下,我想目前「App」一詞所代表的意義,更貼近資料展示平台、特殊的/靈活的/便攜的服務提供管道,開發者要思考的是如何透過它來提供服務,以及可以帶給使用者何種價值。

資訊人應該隨著時下潮流,並動手嘗試,說不定能發現各種新的可能。

2016年7月25日 星期一

Fragment畫面轉向、View.post與getActivity() 聯手演出的問題

問題

某Fragment透過View.post() 待View建立完成後執行相關動作,例如常見的取得View大小,但若post出去的Runnable物件內恰巧包含了getActivity()的相關用法,可能會遇上傳回值為null的情況。

Fragment於Activity.onCreate() 新建並替換現有畫面,在畫面轉向時隨Activity銷毀與重建,但一再新建的Fragment會取代舊有(自動重建)的Fragment,使其與Activity的連結中斷,但已post至主執行緒MessageQueue中的待執行任務仍會被執行,若其中的操作基於getActivity(),將發生Null Point Exception。

這個問題個人不常遇到,但感到挺有趣的,所以花點時間研究一下,若有謬誤麻煩告訴我,有什麼看法也歡迎討論!

還原現場

在MainActivity的onCreate()新建Fragment物件,為求簡便直接以replace方法將畫面置換。

MainActivity.java
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    …
    Fragment fraMain = new FragmentMain();
    getFragmentManager().beginTransaction().replace(R.id.FrameLayoutContent, fraMain).commit();
    …
}

在Fragment的onActivityCreated事件中載入TextView,並以Runnable包裹待View建立完成後要執行的動作,動作內包含getActivity()的呼叫。

FragmentMain.java
public void onActivityCreated(Bundle savedInstanceState) {
    super.onActivityCreated(savedInstanceState);
    TextView txv = (TextView)currentFragment.findViewById(R.id.txv);
    txv.post( new Runnable() {
        @Override
        public void run() {
            getActivity()……
        }
    });
}

關鍵之一「畫面轉向」

Fragment 需要附加於 Activity,其生命週期也受 Activity 影響,這點在官方指南中的附圖可以明顯看出。實測畫面轉向時,Activity 確實執行 onDestroy() 與 onCreate(),而 Fragment 亦同。

Activity 與 Fragment 生命週期
(來源:https://developer.android.com/guide/components/fragments.html)


例在本問題中,轉向後會有兩個Fragment被建立:
  1. 隨Activity重建亦自動重建的Fragment → 原有重建的
  2. 當Activity再次執行onCreate所新建Fragment → 新建的
自動重建的 Fragment 一樣會依序呼叫 onAttach()、onCreate()、onCreateView() 與onActivityCreated(),且在這些方法內呼叫 getActivity() 仍會取得物件,因此不能在一開始的onAttach() 中判斷 getActivity() 是否為 null,作為是否繼續執行的依據。當然,在真正用到getActivity() 的當下,都判斷一次是否為 null 就可避免問題,但這很累人,讓我們繼續挖掘其原理。

關鍵之二「View.post()」

View.post 傳入的 Runnable 物件將被排入主執行緒 MessegeQueue 等待執行。然而隨著Activity 的 onCreate() 執行,一再新建並且 replace 的 Fragment 物件會使舊有 Fragment 銷毀,但已 post 去出的 runnable 仍在主執行緒中等待執行,當 runnable 被執行時,裡面的getActivity() 自然會取得 null,因為該 Fragment 已onDetach(參考前面的生命週期圖)。

解決方案

經過上述分析,大致有3方案可以使用:

  1. 直接在View.post() 判斷getActivity()是否為null,這是最簡單直覺的方法。
  2. 設法重用Fragment,在replace前檢查是已有存在的相同Fragment,這可利用Activity的onSaveInstanceState()來實踐。
  3. 利用Fragment的onAttach()方法,該方法自動傳入Activity參考,可在此先準備好要使用的資料與資源,但遷就其生命週期,程式的撰寫方式與執行流程可能需要隨之更動,若需暫存預先取出的資源,亦需考量耦合問題。

當然,直接鎖定Activity使其不能轉向亦可,但總有需要支援旋轉的情況。

參考資料

  1. Android官方文件Fragment
    https://developer.android.com/guide/components/fragments.html
  2. Android Fragment中getActivity()返回null的问题
    http://www.dss886.com/android/2015/08/11/01
  3. After the rotate, onCreate() Fragment is called before onCreate() FragmentActivity
    http://stackoverflow.com/questions/14093438/after-the-rotate-oncreate-fragment-is-called-before-oncreate-fragmentactivi
  4. 通过View.post()获取View的宽高引发的两个问题:1post的Runnable何时被执行,2为何View需要layout两次;以及发现Android的一个小bug
    http://blog.csdn.net/scnuxisan225/article/details/49815269
  5. 如何使用Handler
    http://givemepass-blog.logdown.com/posts/296606-how-to-use-a-handler
  6. Android中Thread、Handler、Looper、MessageQueue的原理分析
    http://blog.csdn.net/bboyfeiyu/article/details/38555547









2016年4月17日 星期日

Raspberry Pi 3 開機自動啟動程式

前言

最近在執行的專案需要在RPi開機時自動執行Python Script,在網路上爬了不少文章,但似乎沒有一篇可以完全解決問題的,多半是設定後沒有任何反應,經過一番拼湊,終於找出可以在RPi 3上正常執行的方法,在這邊記錄一下。

本文內容是基於這幾篇文章編寫,並視實測結果進行調整


原理

將RPi開機選項設定為以GUI啟動且自動登入User Pi,配合修改對應的autostart設定檔實現開機時自動執行某程式。

開機選項設定

設定步驟

  1. 開啟autostart設定檔,位於pi使用者下的 ~/.config/lxsession/LXDE-pi/autostart。視你目前操作情況,選用適當的編輯器開啟即可(例vi, nano, lefapad)。
  2. 在設定檔最下方填入以下指令。請將demo.py替換為你的python程式所在路徑。
  3. @/usr/bin/python demo.py
    
  4. 若設定無誤,重新啟動後應會看到 demo.py 已被執行。
  5. 若不想重啟,可直接下 startx 指令。



2016年2月14日 星期日

安裝Cordova Plugin

前篇「Cordova 安裝與開發流程」講述Cordova開發環境的準備及基本的使用教學,而本篇則是要說明如何在Cordova專案加入Plugin,讓你的專案具有使用相機、GPS、麥克風等諸多行動裝置硬體設備之能力。

以下就以裝載Camera的Plugin為例,在Cordova專案中裝載該插件:


取得與安裝Plugin

  1. 連線至Cordova官方提供的Plugin Search頁面 (https://cordova.apache.org/plugins/),輸入你想查找的關鍵字即可。我們在此輸入「camera」,馬上可以看到對應的Plugin已顯示。
    Codrova Plugins 搜尋頁面



    cordova-plugin-camera


  2. 使用指令 cordova plugin add cordova-plugin-camera,將會把camera插件下載並裝到Project/platforms目錄下的所有平台。


安裝原理簡述

執行plugin add 指令後,系統已在背後悄悄的完成許多異動,例如:


  1. 將Plugin檔案下載至Project/plugins目錄。
  2. 將已下載的「cordova-plugin-camera」安裝至各平台,這邊以Android為例,即為Project\platforms\android\assets\www\plugins 目錄。
  3. 依安裝的plugin 更新 Project\platforms\android\assets\www 目錄下的「cordova_plugins.js」。
  4. Project\platforms\android中的「AndroidManifest.xml」權限異動。
  5. android\src 目錄,新增「org.apache.cordova.camera」Package與所屬java檔。

雖然這些異動有跡可尋,但仍不建議自行修正,照著正規的方法來處理比較妥當。

檢視現有的 Plugin

使用指令 cordova plugin ls 即可。

移除 Plugin

使用指令 cordova plugin rm [plugin_name],例如要移除 Camera Plugin,則下 cordova plugin rm cordova-plugin-camera。若你忘了Plugin的全名,可由前面的 ls 命令找到。

延伸閱讀

Apache Cordova Documentation - Camera, https://cordova.apache.org/docs/en/3.3.0/cordova/camera/camera.html

2016年2月11日 星期四

Java Script 跨域請求

平日寫App慣了,在App呼叫遠端PHP程式存取資料庫是件相當自然的事。但近期有個Case是以Web方式開發,透過AJAX存取另一Server上的PHP時卻罷工了,追查一下發現是跨域請求造成的問題,在這邊筆記一下。


還原現場

  • 有一個存放於遠端Server的PHP程式,它會讀取資料庫並傳回結果,使用的資料格式為JSON。
query.php
<?php
 //存取資料庫,略!
 $arr = array(
  "id" => "a01",
  "name" => "elephant"
 );    
 echo json_encode($arr);
?>


  • 本機有一JavaScript,以jQuery提供的ajax function呼叫遠端PHP取回JSON字串。

search.js
$.ajax({
 method: "POST",
 url: "http://遠端Server IP/query.php",
 dataType: "json"
}).done(function(data) {
 alert(data.name);
}).fail(function() {
 alert("fail");
});


  • 執行看看,將跳出fail的alert。


錯誤訊息

目前主流的瀏覽器皆內建了開發人員工具,這邊以Firefox為例,執行前述提到的query.js,將看到如下的錯誤訊息:




相當親民的訊息,直接告訴你這是跨域請求的問題,缺少了Access-Control-Allow-Origin檔頭。



解決跨域資源請求之問題

在此以這篇文章「js跨域访问,No ‘Access-Control-Allow-Origin‘ header is present on」提供的方法來解。文內亦有提到跨域存取之細節,有興趣可以讀讀看。欲解決此問題,必須在前述的PHP程式加入「header("Access-Control-Allow-Origin: *");」。


<?php
 header("Access-Control-Allow-Origin: *");
 //存取資料庫,略!
 $arr = array(
  "id" => "a01",
  "name" => "elephant"
 );
 echo json_encode($arr);
?>