[Android] 還缺一把刀 Butter Knife

Butter Knife是Android的View注入框架,這個框架使用Annotation註解的方式讓程式碼更加簡潔,也是目前眾多Android熱門專案愛用的第三方函式庫。

Butter Knife

官網
GitHub

安裝方式,在Android Studio的build.gradle加上這一行:
compile 'com.jakewharton:butterknife:8.0.1'

使用之前,要先在OnCreate()加上這行。
在Activity中,Layout元件原本的宣告方式如下:
使用Butter Knife,只要在元件變數前面加上Annotation註解(要注意的是,[email protected]@InjectViews,[email protected]
在Actiivty中,原本的事件綁定方式如下:
使用Butter Knife,[email protected]
官網建議要在proguard-project.txt加入以下指令,以避免建置時運行Proguard程式碼混淆,造成注入的程式被移除。

Butterknife-Zelezny

接著介紹與Butterknife相關的一個小工具 Butterknife-Zelezny,這是一個Android Studio的插件,用來自動生成ButterKnife的注入程式。

安裝方式

Android Studio > Preferences

Plugins > Browser repositores

搜尋"ButterKnide Zelezny",然後安裝

使用方式

R.layout.xxx 處按右鍵選Generate,選擇「Generate Butterknife Injections」,接著會跳出一個小視窗,從這裡去選擇你要注入的變數。

官網的動畫有很清楚的操作步驟。




分類:

[遊記] 加拿大 Niagara Parkway

從瀑布到尼加拉瀑布湖邊小鎮(Niagara on the lake)約半小時車程,建議可以走Niagara Parkway,這段路沿著尼加拉河沿岸,沿途車不多,景色優美,值得慢慢欣賞。





途中會經過Mcfarland House,這房子建與1800年,在1812年戰爭時,被當作英軍及美軍急救醫院中心,是Niagara on the lake 最古老喬治亞式建築物,現已改為Tea House,入內參觀要收門票。







Mcfarland House
5927 Niagara Parkway, Niagara-on-the-Lake


分類: ,

[Android] 地理資訊在手機端的應用

一般包含地理資訊的資訊源有兩種,一種是不常變動的非即時資訊,例如店家行號的位置;另一種是經常變動的即時資訊,例如公車的即時定位資訊。

不常變動的非即時資訊,通常資料量龐大,一般會預先整理(例如資訊源的地理位置只有地址,沒有經緯度,就要事先將地址轉換成經緯度),然後儲存到後台可處理地理資訊的平台,例如MongoDB或Parse,利用後台計算出手機位置於定位點的距離遠近,再將結果顯示在手機上。這樣做的優點是,效能較好,使用後台的運算能力,手機較不耗電,缺點則是,若資訊源的資料有異動,需要重新整理資料匯入後端平台。我過去曾使用過HerokuOpenshift平台的MongoDB,搭配NodeJS處理地理資訊,也用過Parse.com。在效能上的心得是:Parse >> Openshift > Heroku,沒有再細究原因,個人猜測可能與機房位置有關。

經常變動的即時資訊,因為資料無時無刻在變動,無法預先處理和儲存,必須要在前台手機上處理。如果資訊源只提供地址,要先將其轉換成經緯度,再行計算手機與定位點間的距離。這樣做的缺點是,效能不佳,利用手機的運算能力來計算,手機較耗電。

本文將以新北市垃圾車即時資訊為例,分享手機上如何處理經常變動的即時地理資訊。

本文開始

建構資料結構儲存定位點(POI, Point of Interests)

使用ArrayList這種資料型態來處理多筆、需呈現在ListView上的資料。這裡先建立一個Items class。
欄位包括:
  • car:車號
  • time:時間
  • location:現在位置
  • latitude:緯度
  • longitude:經度
  • distance:與定位點的距離(單位公尺,排序用)
  • distanceText:與定位點的距離,顯示用
import java.text.DecimalFormat;

public class Item {
    private String car;
    private String time;
    private String location;
    private double latitude;
    private double longitude;
    private double distance;
    private String distanceText;

    public Item(String car, String time, String location) {
        this.car = car;
        this.time = time;
        this.location = location;
    }

    public String getCarNO() {
        return car;
    }

    public String getCarTime() {
        return time;
    }

    public String getCarLocation() {
        return location;
    }

    public double getLatitude() {
        return latitude;
    }

    public void setLatitude(double v) {
        latitude = v;
    }

    public double getLongitude() {
        return longitude;
    }

    public void setLongitude(double v) {
        longitude = v;
    }

    public void setDistance(double v) {
        DecimalFormat df = new DecimalFormat("#");
        distance = Double.parseDouble(df.format(v));
    }

    public double getDistance() {
        return distance;
    }

    public void setDistanceText(double v) {
        distanceText = distanceText(v);
    }

    public String getDistanceText() {
        if (distanceText == null) {
            distanceText = "無法定位";
        }
        return distanceText;
    }

    private String distanceText(double distance) {
        String result = "";

        if(distance < 1000 ) {
            result = String.valueOf((int) distance) + "公尺";
        }
        else {
            result = new DecimalFormat("#").format(distance / 1000) + "公里";
        }

        if (latitude==0) {
            result = "無法定位";
        }

        return result;
    }
}

建構ListView Adapter

建立一個ListView Adapter,以便將資料載入自訂格式的ListView Item。
import android.app.Activity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.TextView;

import java.util.ArrayList;
import java.util.List;

public class ListAdapter extends ArrayAdapter {

    private final Activity context;
    private final List<item> items;

    public ListAdapter(Activity context, ArrayList<item> items) {
        super(context, 0, items);
        this.context = context;
        this.items = items;
    }

    static class ViewHolder {
        public TextView timeView;
        public TextView locationrView;
        public TextView distanceView;
    }
    @Override
    public View getView(int position, View convertView, ViewGroup parent) {

        ViewHolder holder;
        View rowView = convertView;

        if (rowView == null) {
            LayoutInflater inflater = context.getLayoutInflater();
            rowView = inflater.inflate(R.layout.listitem_realtime, null, true);
            holder = new ViewHolder();
            holder.timeView = (TextView) rowView.findViewById(R.id.tvCarTime);
            holder.locationrView = (TextView) rowView.findViewById(R.id.tvLocation);
            holder.distanceView = (TextView) rowView.findViewById(R.id.tvDistance);
            rowView.setTag(holder);
        } else {
            holder = (ViewHolder) rowView.getTag();
        }
        holder.timeView.setText(items.get(position).getCarTime());
        holder.locationrView.setText(items.get(position).getCarLocation());
        holder.distanceView.setText(items.get(position).getDistanceText());

        return rowView;
    }
}

取得資料

從資訊源取資料,這裡我們可以使用Volley (關於Volley的介紹,可參考這篇文章)。
RequestQueue queue = Volley.newRequestQueue(this);
String url = "http://data.ntpc.gov.tw/od/data/api/28AB4122-60E1-4065-98E5-ABCCB69AACA6?$format=json";

StringRequest stringRequest = new StringRequest(Request.Method.GET, url,
new Response.Listener <String> () [email protected]
 public void onResponse(String response) {
  showData(response);

 }
}, new Response.ErrorListener() [email protected]
 public void onErrorResponse(VolleyError error) {
  Log.d(TAG, error.toString());
 }

});

queue.add(stringRequest);

private void showData(String str) {
 JsonService jsonsrv = new JsonService(this, myLoc.getLatitude(), myLoc.getLongitude());
 ArrayList <Item > items = jsonsrv.fromJson(str);
 listAdapter = new RealtimeListAdapter(this, items);
 ListView listView = (ListView) findViewById(R.id.listRealtimeInfo);
 listView.setAdapter(listAdapter);

}
資料取得後,可利用Gson來解析JSON格式的資料,並將解析出來的資料,利用迴圈,一筆筆載入前面設定的ArrayList資料型態,以方便後續的處理與呈現。
import android.content.Context;
import android.location.Address;
import android.location.Geocoder;
import android.location.Location;
import android.util.Log;

import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;

public class JsonService {

    private Geocoder geocoder;
    private Context context;
    private double current_latitude;
    private double currnet_longitude;

    public JsonService(Context c, double current_lat, double current_lon) {
        this.context = c;
        this.current_latitude = current_lat;
        this.currnet_longitude = current_lon;
    }

    public ArrayList<realtimeitem> fromJson(String str) {

        JsonParser jsonParser = new JsonParser();
        JsonElement el = jsonParser.parse(str);
        ArrayList<item> items = new ArrayList();
        JsonArray jsonArray = null;
        if(el.isJsonArray()) {
            jsonArray = el.getAsJsonArray();
            Iterator it = jsonArray.iterator();


            while(it.hasNext()) {
                JsonObject o = (JsonObject) it.next();

                Item item = new Item(
                          o.get("lineid").getAsString()
                        , o.get("car").getAsString()
                        , o.get("time").getAsString()
                        , o.get("location").getAsString()
                );

                items.add(item);

                try {
                    geocoder = new Geocoder(context, new Locale("zh", "TW"));

                    String address = o.get("location").getAsString();

                    List<address>
 addressList  = geocoder.getFromLocationName(address,1);

                    item.setLatitude(addressList.get(0).getLatitude());
                    item.setLongitude(addressList.get(0).getLongitude());
                    item.setDistance(getDistanceMeter(item.getLatitude(), item.getLongitude(), current_latitude, currnet_longitude));
                    item.setDistanceText(item.getDistance());

                } catch (Exception e) {
                    Log.d(TAG, e.toString());
                }
            }
        }
        return items;
    }

    private double getDistanceMeter(double lat1, double lon1, double lat2, double lon2) {

        float[] results=new float[1];
        Location.distanceBetween(lat1, lon1, lat2, lon2, results);
        return results[0];

    }
}


地址轉換成經緯度

因為資訊源的定位點原始資料,只有提供地址,沒有經緯度,所以要做地址轉經緯度的動作。我們可以利用Android的Geocoder中的 getFromLocationName 來做轉換。
第一個參數是Activity Context,第二個參數是定義地址的語言,以便可以正確轉換,這裡的地址是中文,所以是 new Locale("zh", "TW")

要注意的是,轉換後的結果如果出現Exception,表示轉換失敗,有可能因為地址資料本身的問題(例如數字用的是全形字,或者有錯字),或是Google Map根本找不到這個地址,此時得再針對這些錯誤資料做例外處理和判斷。
Geocoder geocoder = new Geocoder(context, new Locale("zh", "TW"));

String address = o.get("location").getAsString();

List <address>
 addressList = geocoder.getFromLocationName(address, 1);

item.setLatitude(addressList.get(0).getLatitude());
item.setLongitude(addressList.get(0).getLongitude());

計算兩點之間的距離

有了定位點的經緯度,接下來要計算手機位置和定位點的距離,可以利用Location的 distanceBetween (或 distanceTo )方法取得距離。這裡得到的單位是公尺。要注意的是,distanceBetween得到的距離是直線距離,而不是經過路線規劃得出的實際距離,在手機顯示各定位點的距離,主要是讓用戶比較各定位點與用戶距離之遠近,是相對的概念,雖然GPS定位可將距離計算得非常精確,但精確度其實沒有必要,通常我們會把超過一公里的距離,以公里單位顯示,少於一公里的距離,以公尺單位顯示,只顯示整數,捨去小數位。
private double getDistanceMeter(double lat1, double lon1, double lat2, double lon2) {

 float[] results = new float[1];
 Location.distanceBetween(lat1, lon1, lat2, lon2, results);
 return results[0];

}

資料排序

Android ListView沒有排序功能,我們可以利用Java的 Collection.sort 對ArrayList排序,再將排序後的結果載入ListView。這裡用distanceText欄位作遞增排序。
Collections.sort(items, new Comparator <Item> () [email protected]
    public int compare(Item o1,
    Item o2) {
        return Double.compare(o1.getDistance(), o2.getDistance()); // error
    }
});   

顯示

最後把ArrayList透過ListView Adapter,載入到ListView。
ArrayList <Item> items = jsonsrv.fromJson(str);
listAdapter = new ListAdapter(this, items);
ListView listView = (ListView) findViewById(R.id.listRealtimeInfo);
listView.setAdapter(listAdapter);

結論

地理資訊的處理有很多方法,事實上,Google有提供REST API來做地址與經緯度的換算,可是此法每天有查詢筆數上的限制;網路上也查詢的到,使用三角函數、圓周率和地球半徑計算兩點間距離的方法。但我覺得既然Android本身已提供計算方法,用原生的方法來計算是比較好的選擇。當然,也許市面上存在一些LBS第三方函式庫或工具(例如Spatialite,這是一個類似SQLite的空間向量資料庫),但這方面我的涉略不多,也許日後研究之後,再跟大家分享。


分類:

Shadowsocks作者被警察要求刪除GitHub程式碼

一直有看GitHub Trending的習慣,這兩天發現上面冒出了一堆Shadowsocks相關專案(Shadowsocks是著名的VPN專案,中國用戶常使用這套工具來翻牆)。好奇心驅使下,在維基百科上查到了Shadowsocks作者被警察約談,要求停止開發並刪除GitHub上的程式碼。

Shadowsocks相關連結


兩天前才看到作者clowwindy在GitHub的README上寫了一段話,說自己被查水表了,可惜沒截圖備份,昨天再上GitHub時,已經被刪除了。其實作者不知是刻意或疏忽,只刪除了master主幹,沒有刪release tag,然後還有46xx次的Fork數(數字不斷增加當中)。


網路上相關新聞不多,猜測可能是被屏蔽了,不過還是找到幾篇報導或討論:


之前沒聽說過被警察要求刪除GitHub程式碼的事,是說都已經開源了,也有好多人Fork了,Fork視同備份,光刪掉程式碼沒有多大意義吧。就像樹木主幹被砍,只要有種子落地生根,加以時日妥善照顧,又會是一顆大樹,這就是開源的力量啊!況且不只一顆種子,而是46xx顆!





分類:

[遊記] 加拿大 尼加拉瀑布 Niagara Falls

從紐約出發,經過了十個小時的路程,終於抵達加拿大尼加拉瀑布市,它與美國端的尼加拉瀑布市隔著一條彩虹橋,瀑布景緻比美國端好很多。那時候是五月中旬,在紐約有二十八度,可是加拿大的氣溫不到二十度,還下著大雨,聽說前一個月瀑布還在冰凍狀態,好像到的時間點有點不對。

尼加拉瀑布由美國端的美國瀑布和加拿大端的馬蹄瀑布組成,因為是兩個瀑布,所以英文Fall是複數。


今晚要入住的飯店是喜來登飯店Sheraton on the Falls Hotel面瀑布房Fallview Room,這是在Expendia訂的,雖然有折扣,仍是這次旅行中最貴的飯店。入住後覺得很值得,因為天氣不好,又因長途跋涉很累,在房間裡不用外出就可以看到馬蹄瀑布和美國瀑布,晚上還有點燈,雖然房價高,但一輩子可能也就這麼一次,實在很值得。(這篇的瀑布照片,全部是在飯店房間內照的)


開車實在太累了,不想出門覓食,晚餐立馬點了飯店的room service。


隔天早上的早餐,也是叫room service。




最後要建議想來尼加拉瀑布觀光的朋友幾件事:
  1. 紐約不少旅行社有尼加拉瀑布的Tour,這些Tour多半是當天來回,一大早搭遊覽車出發,傍晚再返回,當天來回的行程太趕,大多時候都在拉車,建議最好在尼加拉瀑布住個一兩天。
  2. 交通方面,如果是單純要看尼加拉瀑布,建議從多倫多進入,多倫多到尼加拉瀑布約兩小時車程;如果人在美國,可先搭國內線班機到水牛城,再租車前往,車程不到一小時,這樣可以省下交通往返的時間。
  3. 飯店方面,喜來登的面瀑布房隨然看得到瀑布全景,但角度有點斜,建議可住Niagara Falls Marriott Fallsview Hotel & Spa,這間的角度更好。
  4. 加拿大端的尼加拉瀑布市比美國端大很多,有公園、餐廳、遊樂設施等等,如果有時間,建議可以多停留幾天

Sheraton on the Falls Hotel
5875 Falls Avenue
Niagara Falls, ON L2G 3K7





分類: ,

Bootstrap 4 alpha發佈

今天(8/20)是Bootstrap四歲生日,就在此時,Twitter團隊發佈了Bootstrap 4 alpha版本


這次釋出的v4版本包括 1,100 commits 和 120,000 行程式碼的修改。改善項目包括:

  • 從 Less 遷移到 Sass
  • 改善網格系统(Grid system),以更支援移動應用
  • Opt-in flexbox support
  • Dropped wells, thumbnails, and panels for cards
  • 合併所有 HTML resets 到一個新的模組中:Reboot
  • 全新自行定義的選項
  • 不再支援 IE8
  • 重寫所有的 JavaScript plugin
  • 改善工具提示和 popovers 的自動定位
  • 改善文件

v4的原始碼會放在Bootstrap在GitHub上的v4-dev分支

除了v4之外,Bootstrap也發佈了官方主題(theme)





分類:

[Android] 網路框架 Volley

Volley 是 Gogle 在 Google I/O 2013 所發表的網路框架,主要是為了加強 Android 網路應用的效能。推出 Volley 的原因是:
  • 目前廣泛使用的 HttpURLConnection 和 HttpClient 有已知的問題和bug是不易修復的,甚至 HttpClient 已經不再被維護(Android 6 已經正式移除 Apache HTTP Client
  • 目前在 Android 上抓取網路資料的標準作法,是需要透過執行緒,再把結果送回主程序,過程稍嫌複雜
因此,Google 團隊在 2013 年推出了 Volley,Volley 可說是集非同步的網路通訊的優點於一身,它提供了字串、Json格式資料、圖片等三種請求方法,內部把Http請求和執行緒封裝起來,開發者不必再寫冗長的程式來完成一個資料加載的動作。它還提供了Cache,使得性能上獲得大幅度的改善。目前 Google 建議使用 Volley 來存取資料量不大且頻繁的網路通訊,Volley 在大量資料的傳輸上,效能很差。

關於 Volley 的內部架構,可參考An Introduction to Volley一文。
來源:http://code.tutsplus.com/tutorials/an-introduction-to-volley--cms-23800
Maven上目前沒有Google官方提供的Volley封裝檔,建議下載原始碼後,加入Android Studio自行編譯。Volley原始碼在Android Git函式庫中,連結如下:
https://android.googlesource.com/platform/frameworks/volley

更新:Android 已將 Volley 的封裝檔放到 JCenter 上,因此現在可以使用 Gradle 安裝 Volley 了
compile 'com.android.volley:volley:1.0.0'

範例

String、Json、Image三個的寫法差不多,都是先從URL連結啟動請求之後,將請求加到Queue去處理(請注意,這是非同步的),若成功則在onResponse事件中取得回傳值,若失敗則會在onErrorResponse得到錯誤訊息。

String Request

final TextView mTextView = (TextView) findViewById(R.id.text);
...

// Instantiate the RequestQueue.
RequestQueue queue = Volley.newRequestQueue(this);
String url ="http://www.google.com";

// Request a string response from the provided URL.
StringRequest stringRequest = new StringRequest(Request.Method.GET, url,
            new Response.Listener() {
    @Override
    public void onResponse(String response) {
        // Display the first 500 characters of the response string.
        mTextView.setText("Response is: "+ response.substring(0,500));
    }
}, new Response.ErrorListener() {
    @Override
    public void onErrorResponse(VolleyError error) {
        mTextView.setText("That didn't work!");
    }
});
// Add the request to the RequestQueue.
queue.add(stringRequest);

Json Request

TextView mTxtDisplay;
ImageView mImageView;
mTxtDisplay = (TextView) findViewById(R.id.txtDisplay);
String url = "http://my-json-feed";

JsonObjectRequest jsObjRequest = new JsonObjectRequest
        (Request.Method.GET, url, null, new Response.Listener() {

    @Override
    public void onResponse(JSONObject response) {
        mTxtDisplay.setText("Response: " + response.toString());
    }
}, new Response.ErrorListener() {

    @Override
    public void onErrorResponse(VolleyError error) {
        // TODO Auto-generated method stub

    }
});

// Access the RequestQueue through your singleton class.
MySingleton.getInstance(this).addToRequestQueue(jsObjRequest);

Image Request

ImageView mImageView;
String url = "http://i.imgur.com/7spzG.png";
mImageView = (ImageView) findViewById(R.id.myImage);
...

// Retrieves an image specified by the URL, displays it in the UI.
ImageRequest request = new ImageRequest(url,
    new Response.Listener() {
        @Override
        public void onResponse(Bitmap bitmap) {
            mImageView.setImageBitmap(bitmap);
        }
    }, 0, 0, null,
    new Response.ErrorListener() {
        public void onErrorResponse(VolleyError error) {
            mImageView.setImageResource(R.drawable.image_load_error);
        }
    });
// Access the RequestQueue through your singleton class.
MySingleton.getInstance(this).addToRequestQueue(request);

分類:

[Android] 如何自訂字型,及Font Awesome的應用

這篇要說明如何在Android App中顯示自訂的字型,首先請上網尋找喜歡的字型,以下為幾個提供免費字型的網站,下載字型之前請先留意該字型的版權聲明,以避免侵權:

程式範例

下載字型檔(.TTF)後,在assets資料夾下新增fonts資料夾,然後把字型檔複製到fonts資料夾內。


下面的程式範例是從asset中取得字型檔,然後用setTypeface方法將指定的Text View更換字型。
TextView mtextView = (ImageView) findViewById(R.id.textview1);
mtextView.setTypeface(Typeface.createFromAsset(getAssets()
                    , "fonts/Lato-light.ttf"));

進階應用:使用Font Awesome

接下來再介紹一個關於字型的進階用法,如果有一個需求是要在App裡動態顯示圖形,例如氣象App會隨天氣的變化而改變不同的天氣圖示。如果要用圖檔來顯示天氣圖示,需要準備各種天氣的圖形,以及不同大小的圖形,以因應不同螢幕大小的裝置,這樣的話,要製作非常多的圖檔。

事實上我們可以將Font Awesome的圖示應用在Android App,Font Awesome是一個網頁圖庫,它將網頁常用圖示放到字型檔裡,好處是可以用Text Size來變更圖形大小,用Text Color來變更圖形顏色,而且只要在App放一個幾十KB的字型檔,不需要放一堆圖檔。

例如我們要在App畫面顯示Android圖形,首先要到Font Awesome Cheat Sheet參照相對應的Uncode:fa-android [& # x f 1 7 b;],將對應關係記錄在string.xml。
<string name="fa_android"> & # x f 1 7 b ; </string>


然後比照上例作法,把Font Awesome字型檔放到assets/fonts目錄下,然後在程式引用字型檔,第四行用setText來顯示圖示。
TextView mtextView = (ImageView) findViewById(R.id.textview1);
mtextView.setTypeface(Typeface.createFromAsset(getAssets()
                    , "fonts/fontawesome.ttf"));
mtextView.setText(R.id.fa_android);

Font Awesome
事實上,有不少專案受到Font Awesome的啟發,開發出類似概念的字型圖庫,例如Erik Flowers開發的Weather Icons專案是天氣圖庫。

Weather Icons



分類:

[遊記] 紐約 康寧博物館 Corning Museum of Glass

康寧博物館在康寧園區內,而整個康寧園區座落在紐約州的康寧市,這裡也是康寧玻璃的總部。


康寧玻璃的產品,包括大家熟知的康寧廚具,它是白色特殊玻璃材質,輕薄、耐髒、易洗。iPhone的顯示玻璃,也是康寧的產品,這也是iPhone零組件中,少數沒有換過供應商的。雖然去年一度有藍寶石玻璃商極特先進科技(GT Advanced Technologies Inc.)加入競爭,不過後來藍寶石玻璃的良率不佳,極特先進又被蘋果搞到破產,所以目前的玻璃市場仍是康寧一家獨大。

在2007年iPhone上市前夕,賈伯斯發現用一般顯示玻璃的iPhone方在口袋中,玻璃會被口袋裡的鑰匙刮花,所以緊急連絡康寧CEO Wendel Weeks,是否有硬度更高的玻璃,這就是後來的大猩猩玻璃Gorilla Glass,大猩猩玻璃已經研發到第三代,不只蘋果產品,非蘋手機和平板,甚至是筆電、相機、手表等3C產品也使用。


入口處的玻璃藝術品


因為地理位置的關係,很多紐約的尼加拉瀑布Tour都會選擇康寧博物館做為中停站,這裡有非常多觀光客。


康寧博物館除了有售票的展覽之外,裡面也有禮品部和飲食部,這裡賣的多半是具收藏性質的玻璃藝術品,少見市面上販售的康寧餐具,即使有,也比在台灣PC Home買還要貴。




Corning Museum of Glass
1 Museum Way
Corning, NY 14830, USA


分類: ,

開發者體驗(Developer Experience)

開發者體驗(Developer Experience, DX)是最近興起的一個概念,它很好解釋,反正你把使用者體驗的「使用者」換成「開發者」就是了。提出這個概念的人認為,開發者也是人,在軟體開發的過程中,也應該平等對待。

「開發者體驗」著重於提供開發者完善的開發支援和服務體驗,所以:

  • 要提供開發者可讀性高的文件
  • API文件的各種方法要寫清楚,最好連範例程式也附上
  • 申請虛擬機器VM不要等太久,最好可以在線上表單把機器規格填一填,送出之後就會自動產生
  • 程式部署的程序不要太煩索,最好設計一個按鈕,讓開發人員想部署幾次就部署幾次

也許你會認為以上情境是在作夢,有一家PaaS廠商Heroku,正是用DX的概念在設計他們的對外服務。而這一切,只要擁抱DevOps就有可能實現。

開發人員常覺得自己身處專案組織的最基層,不受人重視,用的是別人不要的舊電腦,接的是前人留下的爛攤子,沒有文件就叫你開發,還得幫專案經理背黑鍋,相當悲慘。而DX這樣的概念,將會讓開發人員第一次感覺到被重視的感覺。



分類:

[JavaScript] 使用Facebook Graph API抓取資料

使用Facebook Graph API來抓取資料,基本上並不難,目前最新的Facebook Graph API是2.3版,有些舊版(V1.0)的語法不能用新版使用,而網路上找到的範例很多卻是用舊版語法寫的,用新版跑起來會出錯,因此建議初學者,直接參考官網上的Facebook Graph API文件,不過其實官網上的說明寫的不夠詳細,範例也不多,得靠開發者自己多嘗試了。

設定

首次使用Facebook Graph API,請先到Developer Portal建立一個新的App。

建好App之後,可以在App Dashboard上看到App ID,請先記下來,程式是使用這個ID來判別這是你的App。

接著要設定Valid OAuth redirect URI,因為Facebook Graph API會限定伺服器的存取,沒有在有效的OAuth redirect URI清單裡的domain name,在進入OAuth認證時會報錯。首先到Setting > Advanced

然後往下拉,會看到Valid OAuth redirect URIs,請加上你所有環境的domain name,如果開發環境是在自己電腦的本機的8080 port,要輸入http://localhost:8080

範例

下面的程式範例會列出登入者按過讚的Facebook專頁
HTML的部分比較單純,只有三行
<button id="fb-login">Login to Facebook</button>
<div id="fb-root"></div>
<div id="userdata"></div>
JavaScript的部分,首先要引用https://connect.facebook.net/en_US/all.js,然後做初始化
FB.init({
         appId: "(your Facebook app id)", 
         status: true,
         cookie: true,
         xfbml: true,
         oauth: true
    });

這裡是取得登入狀態,若登入成功,則取得accessToken
FB.getLoginStatus(function(response) {
      if (response.status === "connected") {
        var uid = response.authResponse.userID;
        var accessToken = response.authResponse.accessToken;
        
      } else if (response.status === "not_authorized") {
        // the user is logged in to Facebook, 
        // but has not authenticated your app
      } else {
        // the user isn't logged in to Facebook.
      }
     });  

最後是按下登入按鈕的動作:

  • 第6行的"/me/likes?limit=1000"是指定要存取的Node和筆數,若成功會將Json回傳到#userdata
  • 第13行是指定權限"user_likes"
$("#fb-login").click(function() {
    //initiate OAuth Login
    FB.login(function(response) {
        // if login was successful, execute the following code
        if (response.authResponse) {
            FB.api("/me/likes?limit=1000", function(details) {
                // output the response
                $("#userdata").html(JSON.stringify(details, null, '\t'));
                $("#fb-login").attr("style", "display:none;");
            });
        }
    }, {
        scope: "user_likes"
    });
}); 


因為JsFiddle無法在iFrame中顯示Facebook認證畫面,所以無法提供demo,請見諒。


分類: ,

蘇迪勒颱風 - 台北市災害動態資訊

這是昨天颱風夜睡不著寫的,資訊源抓的是台北市消防局EOC資料,半小時更新一次,這只有台北市的資料,災情慘重啊。住在台北市的朋友可以查詢一下住家附近的災情。

蘇迪勒颱風 - 台北市災害動態資訊



分類: ,

[遊記] 紐約到尼加拉瀑布

在紐約待了幾天之後,接下來的行程,是要從紐約開車到加拿大端的尼加拉瀑布,然後北上多倫多。

租車

我們是在甘乃迪機場附近取車的,不在市區取車,是當初希望在不熟路況的狀況下,能夠避免進入曼哈頓島市區,後來發現其實沒必要在機場取車。

我們的租車公司是Aloma,拿到的車是Jeep的休旅車,美國車重,底盤厚實,開起來非常穩,但比較耗油,這也讓我圓了一個開吉普車的心願。

因為美東有不少收費站,為了節省時間,有特別跟租車公司租了電子收費的裝置(類似台灣的eTag,它是用吸盤吸附在前檔風玻璃上),這個裝置不一定要租,因為收費站也有人工收費。

美國高速公路的電子收費還滿特別的,一般常見的,是很像台灣未拆高速公路收費站之前的ETC,不過它需要減速到它標示的速度後通過(好像是15mph),通過時會有逼逼聲表示扣款成功,另一種是有閘道的,車子要停下來,等扣款成功後,閘道門才會打開。相較之下,台灣的ETC真是滿先進的。

跨境開車

雖然事先有跟租車公司確認過,美國車可以在加拿大行駛,租車公司也說,車子在北美隨便開沒關係,只要不要開到墨西哥就好。可是心裡還是七上八下的,後來在加拿大一路上只有我們的車是美國車牌,最後到離開加拿大之前,也沒發生什麼事。在美國和加拿大開車最大的差別是,美國的時速單位用英制(哩),則加拿大是公制(公里),不過還好車上的時速表除了英里單位之外,內圈也標示了公里單位,所以不需要自己轉換。

要特別提一下,因為同行旅客是需使用輪椅的人,我怕停車不方便,有特別上網查證,紐約州和加拿大安大略省可以使用國外旅客由政府核發的身障停車證(證件上需有輪椅的識別圖示),停在他們的身障停車位。我在紐約州的Walmart,以及多倫多市區,都有停在身障停車格,把台北市的身障停車證放在檔風玻璃前,都沒收到罰單,所以我想這方式應該是可行的。

路程

預定的路程是從機場旁直接上高速公路,然後沿81,90高速公路一路開往尼加拉瀑布市。可是不熟悉導航的操作,一開始就被帶到曼哈頓市區,上班時間的入城交通擁塞,在車陣塞了好一陣子,好不容易進入曼哈頓,已經過了一個小時,無意間也完成了在第五大道開車的夢想,不過紐約市的道路品質有夠差,馬路都坑坑疤疤的。

根據GPS的估算,全部路程約七個小時可以到達,我們走走停停開了十個小時才到,抵達加拿大端的尼加拉瀑布市,已經是晚上七點了。回程同樣的路程再開十小時,非常累,還好美國高速公路旁有很多mall,開累了可以休息一下。其實當初應該要選擇在多倫多搭機回台灣的,不需要再花一天的時間返回紐約搭回程飛機。

海關

原本以為,美加邊境的海關檢查會像在機場一樣嚴格,結果也是多慮了。加拿大海關只有看一下護照,問個話就過關了,只花了不到三分鐘,連下車都沒有。回程的美國海關,也只多了一個開後車廂的檢查而已,非常順利。順帶一提,加拿大海關入關的印章,是有彩虹橋圖案的。





分類: ,

[JavaScript] 網頁動態產生QR Code的方法

網路上有很多工具可以幫我們把一串文字轉換成QR Code。事實上,有不少現成的函式庫有提供動態產生QR Code,只要引用這些函式庫,就可以在我們的網頁中產生QR Code,以下介紹兩種網頁產生QR Code的方法:

使用JQuery Plug-In: JQuery-QRCode

JQuery-QRCode是JQuery的一個Plug-In,因此在引用這個函式庫之前,必須先引用JQuery Library。此方式是在客戶端產生QR Code。以下是Plug-In的Github連結與官網
我們可以透過RawGit在網頁中直接引用jquery.qrcode.min.js(關於RawGit的說明可參考拙作《善用RawGit存取GitHub Raw File》一文),函式庫的路徑如下:http://cdn.rawgit.com/jeromeetienne/jquery-qrcode/master/jquery.qrcode.min.js

以下是範例(切換上方Tab可檢視程式和結果)

使用Google Chart

Google Chart API是以RESTful的方式,將參數帶入網頁連結,Google Chart API直接回傳一個圖檔。

細節可參閱Google網站的說明 https://developers.google.com/chart/infographics/docs/qr_codes?csw=1

以下是範例(切換上方Tab可檢視程式和結果)




分類:

Copyright © Andy Cheng

Distributed By My Blogger Themes | Blogger Theme By NewBloggerThemes Up ↑