顯示具有 Android 標籤的文章。 顯示所有文章
顯示具有 Android 標籤的文章。 顯示所有文章

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 ::

Change the title of ActionBar

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

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

Fragment切換,另一種方法

在之前的文章「Fragment切換起手式」中我已經介紹了最基本的方法,就是透過Framelayout作為container
然後使用FragmentTransaction的方式來更換Fragment
其實還存在另外一種方法,這邊我將使用Facebook的一個開放原始碼project來示範

main.xml
<LinearLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        android:orientation="vertical"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent">

    <fragment android:name="com.facebook.scrumptious.SelectionFragment"
              android:id="@+id/selectionFragment"
              android:layout_width="match_parent"
              android:layout_height="match_parent" />

    <fragment android:name="com.facebook.scrumptious.SplashFragment"
              android:id="@+id/splashFragment"
              android:layout_width="match_parent"
              android:layout_height="match_parent" />

    <fragment android:name="com.facebook.widget.UserSettingsFragment"
              android:id="@+id/userSettingsFragment"
              android:layout_width="match_parent"
              android:layout_height="match_parent" />

</LinearLayout>
一開始先將所有Fragment的layout都放置在主要Activity的layout中
接著透過hide與show的方式控制Fragment出現的時機

MainActivity.class
        FragmentManager fm = getSupportFragmentManager();
        SplashFragment splashFragment = (SplashFragment) fm.findFragmentById(R.id.splashFragment);

        // fragments[畫面名稱]是個用來存放相關畫面的Class類別陣列
        // 如SPLASH、SELECTION、SETTINGS分別代表登入頁、主選單與設置頁
        fragments[SPLASH] = splashFragment;
        fragments[SELECTION] = fm.findFragmentById(R.id.selectionFragment);
        fragments[SETTINGS] = fm.findFragmentById(R.id.userSettingsFragment);

        FragmentTransaction transaction = fm.beginTransaction();
        for(int i = 0; i < fragments.length; i++) {
            transaction.hide(fragments[i]);

        }
        transaction.commit();
...
private void showFragment(int fragmentIndex, boolean addToBackStack) {
        FragmentManager fm = getSupportFragmentManager();
        FragmentTransaction transaction = fm.beginTransaction();

        //將不需要的頁面隱藏,只顯示想展示的頁面
        for (int i = 0; i < fragments.length; i++) {
            if (i == fragmentIndex) {
                transaction.show(fragments[i]);
            } else {
                transaction.hide(fragments[i]);
            }
        }
        if (addToBackStack) {
            transaction.addToBackStack(null);
        }
        transaction.commit();
    }

這種方式也是切換Fragment的一種方式,他同樣不會影響Activity的生命週期
不過有趣的一點是──在Activity執行生命週期的時候
所有的Fragment也會同時執行生命週期XDDDD
也就是說,若你有寫一些code,想要讓使用者載入該Fragment的時候自動執行
使用這個方法的結果就是──它同時載入了所有Fragment的code,並且同時執行
由於Facebook的這個project我認為它每個主要的Fragment其實沒有什麼太特殊的功能
不然的話我想使用這個方法必須經過再三考慮

References


facebook-android-sdk/samples/Scrumptious at master · facebook/facebook-android-sdk

Fragment切換起手式

在Android版本3.0之後發行了Fragment這個新功能
從此之後就不用每次要開新的畫面就得launch新的Activity啦!
這個新功能可以做到同一個Activity內多個畫面的切換
同時又不影響整個Activity的生命週期(同場加映Fragment與Activity互相對應的生命週期)
以下將展示Fragment切換起手式

首先你會準備幾個檔案:
  1. 執行以下代碼的Activity(這邊以MainActivity.class為例) 
  2. 欲更換的Fragment(這邊以FragmentNearby.class為例) 
  3. 整個畫面的layout(這邊以main.xml為例,在欲更換Fragment的地方放置Framelayout元件,且在本例中該元件id為content)
Fragment切換起手式
// new一個新的Fragment類別物件
FragmentNearby fragmentNearby = new FragmentNearby();

// getFragmentManager, 這邊getSupportFragmentManager是為了向較舊的版本相容
FragmentManager fragmentManager = getSupportFragmentManager();

// 進行transaction
FragmentTransaction transaction = fragmentManager.beginTransaction();

// 對Fragment動手動腳,指令額外包括add, remove, hide, show...etc
transaction.replace(R.id.content, fragmentNearby);

// 確認並執行transaction!
transaction.commit();



References


Fragments | Android Developers

Sending arguments via Android, php, and external java



這是一個AMP架構下(Apache, MySQL, php)與Android連接
經由Android傳遞數值經過php再到伺服器端執行java程式後再返回Android的架構

傳遞數值的關鍵步驟大概如下面所列出:
  1. (Android)以EditText取得使用者輸入的數值,並以HTTP POST方式傳輸給php
  2. (php)取得使用者裝置傳來的數值,將數值傳入Java程式並執行
  3. (Java)執行結束後將結果返回給php
  4. (php)取得結果後以json格式封裝,傳回給使用者裝置
  5. (Android)取得結果,解碼json

1.(Android)以EditText取得使用者輸入的數值,並以HTTP POST方式傳輸給php

取得使用者input
String input = editText.getText.toString
將input封裝到params這個List裡
params.add(new BasicNameValuePair("input", input));
用HTTP POST的方式送出資料
httpPost.setEntity(new UrlEncodedFormEntity(params));

2.(php)取得使用者裝置傳來的數值,將數值傳入Java程式並執行

取得使用者input
$input= $_POST[‘input’];
執行外部程式,並代入參數
exec(“java –jar path/to/file.jar input”, $output);

3.(Java)執行結束後將結果返回給php

執行Java
public static void main(String[] args){
String input = args[0]; (中間愛幹嘛就幹嘛) System.out.print(結果); }

4.(php)取得結果後以json格式封裝,傳回給使用者裝置

取得Java傳回來的結果(它是陣列)
$result = output[0];
以json格式封裝
die(json_encode($result));

5.(Android)取得結果,解碼json

取得InputStream的內容
Is=httpEntity.getContent();
最後就可以使用BufferReader與JSONObject的方法使用結果了!

HttpClient起手式

當我們用瀏覽器(像是IE、Chrome、Firefox)在逛網站時
可以想像我們的電腦(客戶端,Client)要和網站的電腦(伺服端,Server)一直都在進行溝通
進行溝通的方式大概就是client對server發出要求(request),然後server送回應(response)給client
接著client再想辦法去解析response中所夾帶的資料~

那通常送出request有兩種方法,分別為POST和GET
兩者之間有一點點不同,但這不是這次要講的內容......
這次要說的是Android和網頁或網站傳遞資料的過程當中
所會用到的HttpClient與相關的常用程式碼寫法:

HttpClient起手式

//new一個新的HttpClient物件
DefaultHttpClient httpClient = new DefaultHttpClient();

//看是要用POST或GET方法,new一個出來,並且指定網址(url)
HttpPost httpPost = new HttpPost(url);

//將要傳遞的參數放到params裡面並設定HttpPost的Entity
httpPost.setEntity(new UrlEncodedFormEntity(params));

//new一個HttpResponse來裝httpClient執行之後的結果
HttpResponse httpResponse = httpClient.execute(httpPost);

//使用HttpEntity把內容拿出來
HttpEntity httpEntity = httpResponse.getEntity();

//(範例)內容可以應用在InputStream中
is = httpEntity.getContent();

關於params(傳遞參數,parameters)的設定,它必須使用NameValuePair格式儲存
List<NameValuePair> params = new ArrayList<NameValuePair>();
params.add(new BasicNameValuePair("username", username));
params.add(new BasicNameValuePair("password", password));

延伸問題一、HttpClient好像也很常見,和DefaultHttpClient的差別?

DefaultHttpClient是一個類別(Class),HttpClient是一個介面(Interface)
基本上藉由對HttpClient的實作已經可以應付大部分的狀況 (Default implementation of HttpClient pre-configured for most common use scenarios)
DefaultHttpClient之後也不建議使用了(Deprecated),可以使用HttpClientBuilder、CloseableHttpClient等其他方法

延伸問題二、和透過PHP表單輸入的差別?


在PHP中得透過$_POST["name"]的方式來取得表單中的數值,這邊則得要設定params

References


DefaultHttpClient (Apache HttpClient 4.3.6 API)
DefaultHttpClient is deprecated
【Android进阶学习】Http编程之HttpClient

Integrating Facebook in Android - Parse

參考Parse的教學

我們的目的是使用Parse API結合Facebook SDK

並且做出一個可以撈到FB使用者資料的簡單APP

根據教學,我們在開始coding之前,有兩個前置作業要做:
  1. 申請一個Facebook APP
  2. 把Key hash丟到Facebook APP中,可以參考FB官方流程
其實按照Parse官方的教學走就沒什麼問題,但是有些還是可以提出來一下

產生Key hash

按下你的「Windows+R」組合鍵開啟執行,輸入cmd打開命令列,準備開始製作*.keystore檔案,輸入
keytool -genkey -v -keystore android.keystore -alias android -keyalg RSA -keysize 2048 -validity 10000

程式碼中「-keystore android.keystore」這邊可以自行修改成你要的*.keystore,如banana.keystore

「-alias android」也可以自行修改你要的數值,如banana

輸入後它會叫你填一些東西,乖乖填完就好囉

填完資料會產生一個keystore,我們接著要來取出Key Hash,輸入
keytool -exportcert -alias apa -keystore apa.keystore | openssl sha1 -binary | openssl base64

就可以取得key hash囉!


產生Key hash 問題排除


  1. 如果有困難,無法取得key hash,可以先在cmd中cd到.android資料夾(像我的keystore就是放在 C:\Users\User\.android這個地方)
  2. 下載OpenSSL,把openssl.exe丟到存放*.keystore的地方,我的是放在C:\Users\User\.android


「Invalid Scope:basic_info. Use public_profile,user_friends instead...」問題

問題出在於onLoginButtonClicked()這個function裡面,basic_info是Facebook舊的permission
把它換成新的public_profile, user_friends之類的就沒問題了。

「can't import com.facebook...」問題

開發和網路有互動的APP時一定要記得開網路呀XD

「could not find *.apk」問題

我在用Eclipse的時候不停的build和clean,最後怒點右鍵點property→Android
在SDK那邊點了isLibrary,把這個勾勾取消掉就好囉


最後可以到這幾個地方看看自己的成果

  1. Facebook Developer 選擇自己的APP
  2. Home - Parse  選擇Parse Core

Parse.com 相關連結收集

前一陣子有一則很熱門的新聞,Facebook將一位高中剛畢業的實習生──Michael Sayman升為正職,而他也成為了Facebook團隊中最年輕的工程師。詳細的故事可以參考「大家都羨慕臉書的18歲天才工程師,但他到底「憑什麼」?」這則新聞。閱讀完畢後我發現了Parse這項服務,雖然之前就曾經聽說過了,但是並沒有仔細的去了解;現在知道了Parse是一個方便的工具,讓開發人員不用顧慮後端server環境的建置,可以將全部的心力投入在創意、與開發流程上。

Parse是什麼?

Parse.com 為終端使用者(end-users)提供了後台的管理平台,它支援了iOS、Android等還有網頁等平台。它包含的功能如下:
  • 資料保存
  • 推播通知(類似FB的紅色點點或是有人傳簡訊給你的時候會收到的通知)
  • 社交服務整合
  • 資料分析
  • 雲端編碼
  • 網頁建置


連結

Parse tutorials - 官方的教學文件(英文)
Parse.com 超簡單 架Mobile Android Server教學 - 快速上手