Use SQLite to record user history

如果我們想記錄使用者在APP上互動的歷史,我們可能會需要紀錄她在某個時候,做了什麼樣的一個動作。在Android中有很多可以進行紀錄的方法,其中SQLite API最被廣為人知的應用就是用來實作TodoList、通訊錄或是其他牽涉到Client端資料庫的應用,而這邊我們也使用SQLite來進行紀錄使用者紀錄。
SQLite的教學可以參考Vogella,寫得相當完整且英文描述也非常的平易近人
教學文中主要將使用SQLite存取資料的功能分成三個物件
包括了:

  1. 自訂的SQLiteOpenHelper(用來執行資料庫的一些任務)
  2. Getter & Setter(存取資料)
  3. 還有資料存取物件(Data Access Object,DAO,為連結以上兩者的橋樑)

以下分別介紹這三者的起手式。

Custom SQLiteOpenHelper

public class DBHelper extends SQLiteOpenHelper{

 public DBHelper(Context context) {
                // DATABASE.NAME(String)與DATABASE.VERSION(int)都是我自訂interface的數值
                // 分別代表資料庫的名稱與版本
  super(context, DATABASE.NAME, null, DATABASE.VERSION);
 }

        // 資料庫一開始建置時會執行的動作將在onCreate生命週期中觸發
 @Override
 public void onCreate(SQLiteDatabase db) {

                // 這邊和SQL很像,就用你熟悉的方式輸入指令
  String sql = "CREATE TABLE IF NOT EXISTS " + DATABASE.TABLE +
    " (" +
    " " + DATABASE.UID + " TEXT," +
    " " + DATABASE.DID + " TEXT," +
    " " + DATABASE.TIME + " DATETIME," +
    " " + DATABASE.RID + " TEXT" +
    ");";
  
                // 執行
  db.execSQL(sql);
 }

        // 若發現更新後的資料庫(版本不一樣時),刪除原本的資料庫,並建置一個新的
 @Override
 public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {

  String sql = "DROP TABLE IF EXISTS " + DATABASE.TABLE;
  
  db.execSQL(sql);
  onCreate(db);
 }
}

Getters & Setters

public class UserHistory {

 private String uid;
 private String did;
 private String time;
 private String rid;

 public String getUid() {
  return uid;
 }

 public void setUid(String uid) {
  this.uid = uid;
 }

 public String getDid() {
  return did;
 }

 public void setDid(String did) {
  this.did = did;
 }

 public String getTime() {
  return time;
 }

        ... // 以此類推

Data Access Object

// 這邊是最重要的部分,所有有關資料存取的動作我們都在這裡調用
// 如此一來就不需要動到以上兩個物件,妥善隱藏資料的本質
public class HistoryDAO {

 private SQLiteDatabase database;
 private DBHelper dbHelper;

 public HistoryDAO(Context context) {
  dbHelper = new DBHelper(context);
 }

 // 開啟資料庫
 public void open() throws SQLException {
  database = dbHelper.getWritableDatabase();
 }

 // 關閉資料庫
 public void close() {
  dbHelper.close();
 }

 // 輸入資料到資料庫中
 public void insert(String uid, String did, String rid) {

  // 使用key/value的方式儲存資料,再丟給資料庫insert
  ContentValues values = new ContentValues();
  values.put(DATABASE.UID, uid);
  values.put(DATABASE.DID, did);
  values.put(DATABASE.TIME, getCurrentTime());
  values.put(DATABASE.RID, rid);

  database.insert(DATABASE.TABLE, null, values);
 }

 // 因為我們在台灣,需要調整時區,以獲得使用者產生互動的正確時間
 private String getCurrentTime() {
  // 註1
  SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
  dateFormat.setTimeZone(TimeZone.getTimeZone("GMT+8"));
  
  String currentTime = dateFormat.format(new java.util.Date());
  return currentTime;
 }

 // 這邊因為我在測試是不是真的有輸入成功,於是在進行query後取得數量
 public int size() {
  Cursor cursor = database.query(DATABASE.TABLE, new String[] { 
    DATABASE.UID,
    DATABASE.DID,
    DATABASE.TIME,
    DATABASE.RID }, null, null, null, null, null);
  
  return cursor.getCount();
 }
}

註1:時間的格式有標準的規定,例如年份並不支援大寫Y。
SQLite也很有個性,有它自己能接受的時間格式。(繁體中文版本)

藉由以上三個物件的實作,已經可以應付最基礎的資料存取功能。
儲存完使用者的歷史紀錄之後,接著就要開始著手偏好分析。

Reference,


JAVA的日期時間取得 @ ROACH部落落 :: 痞客邦 PIXNET ::

The Future of Web

由於我的研究領域牽涉到了本體論(Ontology)
也就是Tim ‎Berners-Lee(2004)提出的語意網Layer Cake中的一個部分:


因此我略懂RDF也是很正常的。
就像身為一個汽車維修員,隨身攜帶工具也是很正常的一樣XD
所以以下此篇將以語意網的角度淺談Web的未來。

一開始,讓我們先來了解一下Web 1.0 2.0與3.0的不同:

分類(Web)
描述
1.0

沒有互動的機制,所有的資訊都是單向的流通。
2.0

最主要的精神是可以進行協作與共享,模範生有Wikipedia,Facebook等等。
「Anyone can say Anything on Any topic」這個AAA Slogan[1]也將其精神表現得淋漓盡致。
3.0
根據維基百科的定義:
「該詞包含多層含義,用來概括網際網路發展過程中可能出現的各種不同的方向和特徵,包括:將網際網路本身轉化為一個泛型資料庫;跨瀏覽器、超瀏覽器的內容投遞和請求機制;人工智慧技術的運用;語意網。」
※泛型資料庫:我認為是與Linked Data相關概念,請參考Vcard。


語意網中有一層標準和現在的HTML標準很接近,但是又有點些許的不同。
這層便是RDF。(在Semantic Web Layer Cake中Ontology的下層)
和HTML同為XML檔案,且都是W3C定義的標準,其簡介如下:

RDF 語言是 W3C 的語意網活動的组成部分。W3C 的「語意網展望(Semantic Web Vision)」的目標是:
  • Web 資訊擁有精確的涵義 
  • Web 資訊可以被電腦理解並處理 
  • 電腦可以從 Web 上整合資訊

以下是RDF的範例代碼。在RDF中,所有的標籤皆可以是自訂的:
<?xml version="1.0"?>
<RDF>
  <Description about="http://www.w3school.com.cn/RDF">
    <author>David</author>
    <homepage>http://www.w3school.com.cn</homepage>
  </Description>
</RDF>
而從HTML4、HTML5甚至到HTML6來看
其實對於自定義標籤的限制是越來越寬了
從一開始的head、body,到後來還有header、footer等標籤
某種程度看來也是在幫助網頁本身的資訊能夠擁有更精確的涵義

就現在來說,我並不確定我們屬於Web的2.0、2.3、2.5的哪個階段
但可以確定的是,我們已經慢慢地朝著Web 3.0的方向前進
至於4.0是什麼樣的世界 真的很難以想像......

References,


[1] Semantic Web for the Working Ontologist. D.Allemang & J.Hendler, 2008.

Resources of implement custom TabHostButton, ListView with images and texts, and ProgressBar when loading data


Mealionaier是一個可以學習你對食物的偏好
精準推薦給你附近美食的APP。

然而為了更好的使用者體驗,需要實現方便使用、直覺的UI
在實現某些UI時需要一些tricks。

在這邊和大家介紹我之前碰壁時,有如菩薩般出現拯救我的資源
等我論文告一段落了,再來分享詳細的程式碼教學:)

利用RadioGroup與Fragments創造Tab Buttons效果


Tutorial link - 开源项目AndroidUtil-采用Fragment实现TabHost | 方杰
  • 每個Tab都是一個RadioGroup的項目。
  • 為RadioGroup新增它的項目符號後,再將選取按鍵(checkbox)隱藏起來
  • 當使用者點擊Tab時,用程式的方式選取對應的項目。


在ListView中妥善顯示圖片與文字


Tutorial link - Android Adapter Good Practices « Piwaï.info
  • 做好忍痛放棄ArrayList、SimpleAdapter等內建Adapter的心理準備
  • 繼承BaseAdapter,自訂一個新的Adapter from scratch
  • 了解ViewHolder的patterns

動態載入ListView的items並在載入時顯示ProgressBar


Tutorial link - Endless Scrolling with AdapterViews | CodePath Android Cliffnotes
  • 學會操作onScrollListener
  • 繼承BaseAdapter,自訂一個新的Adapter
  • 深入了解如何操作getViewType、getView等BaseAdapter中的method

Git rebase 實戰演練

自從使用Git之後,因為開始變得我天不怕地不怕
有時候上個廁所也好,出門覓個食也罷
毋忘隨時來個commit,想說養成save的好習慣
所以以下的事情是家常便飯XD

久而久之一個專案內不小心就充滿了凌亂的commits
 
Git會記錄我們coding的一舉一動
我們已經知道藉由使用branch可以方便的讓我們切換版本
但是魔鬼藏在細節裡
真正紀錄我們細節的,其實就是那些年,無時無刻盯著我們
吃飯、睡覺、打code的commits

當我們每次進行commit的時候,都會有相對應的SHA碼
(SHA是一種演算法,在這邊就當成它是經由複雜的運算,來達到讓每次進行commit的時候,都不會有重複的狀況而採取的一種紀錄方法)
就好像我們每個人都有自己的身分證字號一樣
你可以在這邊找到你的commit他們的身分證字號

Github(網頁版)


Github(桌面版)


很棒吧,他們都已經排排站了喔
紅色框框的地方是你點一下就可以作一個複製的動作。
那今天就是要來示範如何將多餘的commit進行一個整理的動作
讓本來凌亂的紀錄,連小明都看得懂。

首先呢
請移駕到你的command line,我用的是Github提供的那個工具
先cd到你專案的資料夾
git rebase -i [SHA/commit版本號碼]
我這邊直接開最舊的起來改,中途會彈出一個文字檔,你的看起來應該也和下面的差不多:
pick e466349 Loading files
pick 8af9b37 Create README.md
pick 161534d small revision
pick d992172 small revision
pick 2cf85cd easy connected
pick 4769884 Nov 20
pick 92b4484 Nov 21
pick 30d9919 Nov 21 noon
pick bc92c4a Nov evening
pick 4771349 Nov 22
pick 266f43b Nov 23
pick a1990a4 Nov 24
pick 2100e46 Nov 24 v2
pick 196b0dc Nov 25

...

# Commands:
#  p, pick = use commit
#  r, reword = use commit, but edit the commit message
#  e, edit = use commit, but stop for amending
#  s, squash = use commit, but meld into previous commit
#  f, fixup = like "squash", but discard this commit's log message
#  x, exec = run command (the rest of the line) using shell
在這邊你可以自己一個一個commit慢慢玩
你會發現最舊commit它保管了最多的pick
換句話說,越舊的commit提供你更大的調整範圍
你可以根據自己的需求來進行pick reowrd edit squash fixup exec等動作
這些動作我想以後再介紹,今天重點就在使用squash

pick和squash最主要的不同是
當你用rebase指令開啟了一個commit
你會看到很多個pick(如果它不是最新的commit)
而當你把其中幾個pick註解掉或是刪掉的話
沒錯,它們就會像變了心的女朋友一樣
頭也不回的走了,永遠都回不來了!

那如果你是用squash的話,就很像是吾皇擁有後宮三千佳麗
卻無奈於經濟不景氣,本來一張鈔票可以買一碗牛肉麵
現在只能買一碗滷肉飯......所以把三千砍到剩千五的道理一樣
squash是向前融合(meld into),所以它的前一個動作不可為squash
(拜託不要呀~千五已經很少了......)

以我自己的專案為例,我只挑了最重要的幾條沒有squash
剩下的都向前融合(這邊用#會讓Git跳過不讀,等於註解):
#一開始的檔案
pick e466349 Loading files
squash 8af9b37 Create README.md
squash 161534d small revision
squash d992172 small revision
squash 2cf85cd easy connected
#建立主機伺服器連線
pick 4769884 Nov 20
squash 92b4484 Nov 21
squash 30d9919 Nov 21 noon
#動畫完成
pick bc92c4a Nov evening
squash 4771349 Nov 22
squash 266f43b Nov 23
squash a1990a4 Nov 24
squash 2100e46 Nov 24 v2
squash 196b0dc Nov 25
squash 2e6cbcd Nov 25 v2
squash bebe100 Nov 25 v3
...
接著請儲存後關掉記事本
沒有意外的話等一會又會有記事本彈出來和你報告:
# This is a combination of 5 commits.
# The first commit's message is:

Loading files

# This is the 2nd commit message:

Create README.md

# This is the 3rd commit message:

small revision

# This is the 4th commit message:

small revision

shake new activity

# This is the 5th commit message:

easy connected

# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
# rebase in progress; onto 4f12ce8
# You are currently editing a commit while rebasing branch 'ManualEntry-v2' on '4f12ce8'.
#
意思就是
「稟告皇上,在下已經把舊的幾個宮女fire掉了
然後請了一個最新的代替那些位置,您要為她取一個新的名字嗎?」
其實你可以把他們全部砍掉,留一個名稱就好。
「朕,覺der可以叫做Basic Settings。」
「喳!」
Basic settings

# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
# rebase in progress; onto 4f12ce8
# You are currently editing a commit while rebasing branch 'ManualEntry-v2' on '4f12ce8'.
#

一樣儲存後關掉記事本
中途你可能會遇到重複的現象,一直叫你取名幹嘛的
不過照著範例走,應該不會有問題。

Finally,我們就完成了整理commits的動作。
來ㄧ張selfie~


References


Reflection on using Git

這篇將依照目前使用Git的心得,簡單的對Git與提供保存Git與原始碼在雲端的網路服務Github進行介紹。

Git是什麼? 為什麼要用Git?
Git是一種版本控制的系統。
換句話說,Git可以提供你整個專案版本的監控
藉由使用版本控制系統,你可以自由地在每個版本之間切換
你可以測試不同的版本並且備份你的版本。

Git的備份功能和傳統我們認知的備份有很大的不同。
依照傳統的作法,我們在進行一個專案時,若遇到不同的想法想測試,
我們可能會先將檔案(或整個專案)複製一份,並且用複製出來的新版本進行修改
原版就先進行保存。所以這時候你可能會遇到這種情況:



之後當我們的每個版本都有一定程度的發展,你可能會遇到這種情況:



這種情況我認為是各有優缺啦:
Pros
Cons
  1. 無腦,直覺,不須傷腦筋。在當下可以快速的分離出各種版本,並確定未來的發展。

  1. 檔案一多管理起來將會很傷腦筋,並且很難依照命名來推測出每個版本之間細微的差異。 
  2. 重複的檔案佔額外的容量。

依照傳統的方式,若本來要發展的版本分岐不大,確實可以使用這種方法,因為若沒有進入後續維護的階段,其實非常的省時省力。但一旦踏入了維護的階段,看到那排山倒海而來的檔案......你可能會全身無力......

那使用Git呢? 由於Git是一個工具,我們在學習一樣工具如何使用的過程中多多少少會耗費一些學習的時間,但就我用到目前為止而言,覺得經濟效益確實比傳統的作法好上太多。使用Git需要使用命令工具列(也就是按壓鍵盤Windows+R後,輸入cmd會出現的東西)輸入一些命令,但有了Github提供的圖形化介面之後,其實已經好用很多。
使用Git的優缺點:
Pros
Cons
  1. 自由自在的切換版本(註1)。(尤其在開發Android常常會有同時會牽涉到程式碼與外觀的部分,這時候可以很快的切換版本並執行)
  2. 自由自在的查看版本內每個紀錄的細節(註2)。(像是這次又加了什麼新功能,把什麼東西改掉了)
  3. 備份的方式很像是幫你建立一個還原點,而沒有複製重複的檔案。
  1. Git的使用方式需要學習,且Github與Github提供的圖形化介面軟體目前沒有中文版本。

以前我用傳統的備份方式,有時候常常在不同版本的專案資料夾內把檔案拉來拉去,後來也忘記這些名字一樣的檔案到底哪個是有改過哪個是沒有改過的;或是說有時候不小心在系統詢問要不要取代時不小心按了取代,然後心血便付諸流水。

至少現在我用了Git,我能很肯定地說我可以很大膽的coding,我可以肆無忌憚的對程式碼大興土木,只要在每個關鍵點記得做一個記錄的動作,之後就算我改壞了,一行指令就回來啦。這時候你真的可以體會什麼叫作天無絕人之路、什麼叫作浪子回頭金不換XD

學習使用Git可以參考:
  1. 歐萊禮出版的版本控制使用Git(第二版),內文平易近人。
  2. 繁體中文的官方Git使用手冊,圖文並茂好消化。
註1:Git的checkout
註2:Git的commit

延伸閱讀:我的Git常用指令(準備中)

Change the title of ActionBar

如何改變ActionBar上顯示的標題呢? 如
123

在OnCreate()中加入
setTitle("安安你好嗎");

Solve the json encoding problem of Chinese characters in Java

今天想說要把資料從資料庫中擷取出來顯示在Android的使用者介面上
但因為我是使用php對MySQL做查詢
再將查詢的結果用陣列encode成json格式,再傳回Android
然後我有一個習慣就是我會先在Eclipse的console面板的log中先看傳回什麼東西XD
例如
Log.e("result", json)
於是就看到我的log跑出一堆很奇怪的訊息,類似
\u6211\u662f\u5e25\u54e5
就像這樣,每個都有\u開頭
後來花了一點時間查了一下資料,了解了這原來就是所謂的萬國碼(Unicode)
然後其實也不用花費時間在轉碼上焦頭爛額......因為
就算log顯示的是那樣,Java在輸出(print、TextView)的時候還是會自動轉碼回去
log的時候會產生這樣的結果,是因為json在encode的過程中會將中文自動轉碼
保險一點的話還是可以在語法中加入JSON_UNESCAPED_UNICODE
json_encode($string, JSON_UNESCAPED_UNICODE);
這是php版本5.4以後才有的功能
如此一來json在encode的過程中就不會強迫轉碼囉

References


The Haversine formula

在我們設計App時,有時候我們會需要獲得使用者的地理位置資訊
然後用使用者目前的經緯度座標,計算離使用者地點最近的幾個地點
這時候如果你的資料庫中有存放一些地點的座標資料
那我們就可以使用所謂的Haversine公式,來算出地球上使用者與這些地點座標之間的距離


(簡單的解釋一下每個符號的意義:d是距離,r是弧度,φ是經度,λ是緯度)
然後假設我們資料庫中有一個資料表(table)
且資料表中的每個欄位記錄了每個地點的名稱(name)與座標(lat, lon),例如
table
name
lat
lon
name_1
latitude_1
longitude_1
name_2
latitude_2
longitude_2
...
...
...

接著我們可以將上面的公式轉換一下並且假設我們現在的座標在(23, 120)
在資料庫中就可以用這個語法來查詢
SELECT
     name,
     ( 6371 * acos( cos( radians( 23 ) ) * cos( radians( lat ) ) * cos( radians( lon ) 
     - radians( 120 ) ) + sin( radians( 23 ) ) * sin( radians( lat ) ) ) ) AS distance
FROM
     table
ORDER BY
     distance   
結果就會顯示這些地點的名稱與使用者之間的距離(公里),並且已經依照遠近排序
其中用到的acos()、cos()、sin()、radians()等函數,在MySQL中都可以直接使用

Reference


Haversine formula - Wikipedia, the free encyclopedia
Creating a Store Locator with PHP, MySQL & Google Maps - Google Maps API — Google Developers

Public variables and methods

最近上數位學習的課,正在討論SCORM(Sharable Content Object Reference Model)
SCORM真的是個很神奇的傢伙!
它可以壓縮成package,接著上傳Moodle
然後學習者的一舉一動都會被監視XD
(例如:學習後進行測驗的分數、已經進行學習與否還有學習所耗費的時間等等)

其中談到SCORM的架構時,有說到SCORM使用了標準(standard)
來決定要儲存學習者的哪些資訊
也就是追蹤學習者學習的最小單位(SCOs)所用到的技術

在這過程中不小心就聯想到public變數與method的使用XD
所以就來複習一下吧 :P  這次要探討的是

為什麼在修改資料時,與其直接操作public變數,另外建立method反而是更好的做法?

以下我將用箱子還有球來示範,應該是夠淺顯易懂吧~

使用public變數

public class Box(){
     //假設我們找來了一個箱子(它是一個物件)
     //並且在裡面放進了5顆球,想說等一下要分別發送給5個人
     int ball = 5;
}
然後...有人看到了這個箱子
Box box = new Box();
box.ball = 0;                     //噢不...球球被拿光光了T_T

然後看看這個方式

public class Box(){
     //一樣放進5顆球
     private int ball = 5;
     public Box()

     //規定一次只能放一顆
     public void getOnlyOneBall(){
          ball = ball - 1;
     }
}
之後有人要拿
Box box = new Box();
box.getOnlyOneBall();  //一次只能拿一顆吧!你看看你~
Yes! 這次狀況就好很多了。
所以說呢可以使用一些特別的方法(method),避免有心人士一次就把球球拿光光
或是在不小心的狀況之下本來要放4顆,結果放進了10顆(意外對變數造成修改)

在大多數情況下,我們都希望能夠自由地對變數進行操作。但是誠如Clean Code無瑕的程式碼一書所說:
「將實現的過程隱藏實際上便是一種抽象化的過程。而在這當中,類別不單單只是透過讀取及設定函式讓變數供人存取而已;To be more specific, 它提供了一個抽象介面,讓使用者在不需要知道實現過程的狀態下,還能操縱資料的本質。」
改變世界的九大演算法一書中也有提到:
「撰寫程式碼的原則是盡量隱藏資料。」