使用 Scala 開發 Android 的環境建構

開發 Android App 的語言選擇不外乎 Java 。 但使用 Java 開發卻是一點都快樂不起來,那就改用 Scala 吧。

使用 Scala 開發 Android 之前得先建構好開發環境,接下來就按部就班的建立吧。

1. 下載JDK

http://www.oracle.com/technetwork/java/javase/downloads/jdk-7u3-download-1501626.html 根據自己的環境下載合適的 JDK 。

ubuntu 的環境底下可以透過 apt-get install sun-java6-jdk 快速安裝。

2. 安裝 Scala Complier

先到 http://www.scala-lang.org/downloads 下載 Scala 的編譯器。

因為 Scala Compiler 本身就是使用 Java 撰寫,可以下載通用的版本 ( .tgz ),解開後就可以了。或是下載特定平台的版本,進行自動安裝。

3. 下載 eclipse

請到 http://www.eclipse.org/downloads/ 進行下載。

選擇 Eclipse IDE for Java Developers 版本即可。如果你有開發 Java EE 的需求,則可以選擇 Eclipse IDE for Java EE Developers  。 解開壓縮檔後直接執行目錄裡的 eclipse 就可以了,不用特別安裝就可直接執行了。

4. 安裝ADT ( Android Development Tools )

開啟 eclipse ,點選選單 Help > Install New Software

點選右上方的 Add,加入以下設定。

將 Developer Tool 選項打勾,一路 Next 到底就完成安裝嘍。

5. 安裝 Android ADK

點選選單 Window > Android SDK Manager

勾選你要下載的 SDK 版本,一般來說就下載你常用到的版本就行了,全部下載回來容量也是挺嚇人的。

6. 安裝 Scala IDE

一樣點選選單 Help > Install New Software

Name:  Scala IDE
Location:  http://download.scala-ide.org/releases-29/milestone/site

安裝 Scala IDE for Eclipse 這個項目就行了,Source Feature 就看你有沒有想看原始碼嘍。

7. 安裝 Ant

Ant是一個 java 的build工具。

ubuntu 的使用者可以透過 apt-get install ant 進行安裝。

或是到 http://ant.apache.org/bindownload.cgi 下載,解開即可。

8. 下載 Ant scala build rule

由於 Ant 的 build rule 實在又臭又長,直接下載整理好的 build rule 比較省事一些。

https://RickySu@github.com/RickySu/ant-android-scala.git 直接 clone 一份回來就對了。

完成基本安裝後,就可以開始建立專案。

點選選單 File > New > Project

選擇 Android 專案

專案名稱叫做 helloworld

SDK版本建議選擇 2.1 以上,如果想要搭配 Cordova ( 之前叫做 Phonegap , Callback  … ) 作開發,版本得選擇 2.2 以上。

填好 Package Name ( Package Name 必須是個唯一的名稱,Android 是以Package Name區分不同的App),按下 Finish就完成專案的建立了。

接著在專案底下點選滑鼠右鍵 ,啟用 Scala IDE 支援

這樣就可以啟用Scala嘍。

那我們先來個測試的 Scala Hello world

點選選單 File > New > Other

選擇 Scala Application

已目前的例子,Package Name 叫做 com.app.example.hello3,原本的 Java Activity 叫做Hello3Activity,所以我們建立一個 Hello3Activity 的 Scala Activity。

在 src 底下會看到 Hello3Activity.java 以及 Hello3Activity.scala 兩個檔案。

我們編輯一下Hello3Acitvity.scala

1
2
3
4
5
6
7
8
9
10
11
package com.app.example.hello3
import android.app.Activity
import android.os.Bundle
import android.view.View
 
class Hello3Activity extends Activity{
    override def onCreate(savedInstanceState:Bundle){
        super.onCreate(savedInstanceState)
        setContentView(R.layout.main)     
    }
}

並且刪除掉原本的 Hello3Activity.java。

就開始 Run 嘍。

在Android模擬器中就會看到成果嘍

一整個歡樂的 Force Close。

原因在於,Scala 的所有物件中都會包含一個 ScalaObject 的 Interface 。 這個 Interface 是定義在 scala-library.jar中。但是這個 jar 並沒被 加入到 APK 中。如果自己手動將 scala-library.jar 加入到專案的 libs 裡面,整個 APK 可是會肥到爆炸。 ( 這個 jar 可是有將近8MB) 因此我們得動用 Ant 以及 ProGuard 這兩個工具來進行編譯的動作。

Ant 是 java 專案使用的 build 工具。ProGuard 則是 java 的混淆間瘦身的工具,可以將編譯後的 .class 去除掉沒用到的部份打包成 .jar,順便也提供名稱混淆,防止被反編譯。

還記得前面 步驟8 的 build rule,把 ant-android-scala clone 回來後,將 local-example 目錄底下的 build.xml , local.properties 複製到專案的根目錄底下。

修改 local.properties 的內容

sdk.dir=/home/ricky/eclipse/android-sdk-linux
ant.android.scala.dir=/home/ricky/scala-android/ant-android-scala
scala.dir=/opt/scala-2.9.2

根據個人的實際的環境設定修改。

由於 eclipse 內建了一個 Ant ,也可以透過 console 端去執行 Ant ,這邊我們先介紹使用eclipse的 Ant 來執行 build 工作。

選擇 debug 執行 Run 後,就開始 build 出 debug 版本的 apk 了。

接著重複上面的動作, 改選擇 indestalld 就會把剛剛 buid 出來的 apk 安裝到模擬器中。

我們在模擬器上執行一下剛剛安裝進去的 apk 。

可以正常執行嘍。

在 console 端執行 build 就簡單多了。

只要在專案根目錄底下執行

$ant debug
$ant installd
發表於 Android, 程式開發 | 已標籤 , | 發表迴響

phpconf 2011 – Symfony簡介

View more presentations from Ricky Su
以下是一些相關元件的詳細介紹
發表於 Symfony, 網頁開發 | 已標籤 | 發表迴響

如何建立有效的快取

View more presentations from Ricky Su

20111012- Cache by Ricky Su from mOrris32 on Vimeo.

發表於 程式開發, 網頁開發 | 2 則迴響

讀書會分享-Symfony Admin Generator

投影片:

現場錄影:

Part 1/2

20110831- Zen of Friends by Ricky Su 1/2 from mOrris32 on Vimeo.

Part 2/2

20110831- Zen of Friends by Ricky Su 2/2 from mOrris32 on Vimeo.

有關Config的補充說明連結
http://www.symfony-project.org/reference/1_4/en/

 

發表於 PHP, Symfony | 1 則迴響

Symfony的表單元件Form

撰寫網頁程式表單也是個令人頭大的問題。

有的欄位要使用下拉選單,有的要用textarea,有的要用password欄位…

好不容"畫"好表單後,還得驗證欄位的內容是不是符合規範。
有的必須是數字,有的不能超過20個字元,有些得要符合email格式,甚至某些欄位還得跟db結合驗證,例如帳號不能跟別人重複。

為瞭解決這堆殘害工程師的防呆機制,Symfony設計出了form元件來協助我們解決這些問題。

Form元件包含了幾個主要的部份。

  1. Widgets class — 用來協助"畫"出表單的欄位,例如input text,radio button,textarea …。
  2. Validator class — 用來協助驗證POST的欄位數值。
  3. Form class — 用來協助整合Widgets,Validator,以及Model。

這裡我們以這個db schema為例子(for doctrine)
config/doctrine/schema.yml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
Member:
    actAs:
        Timestampable:     ~
    columns:
        id:
            type:     integer
            primary:  true
            autoincrement: true
        name:
            type:     string(255)
            notnull:  true
        email:
            type:     string(255)
            notnull:  true
            unique:   true
        phone:
            type:     string(20)
            notnull:  true
        sex:
            type:     varchar(1)
            notnull:  true

Timestampable: -> 我們要使用timestampable這個behavior。簡單的說就是要doctrine自動幫我們接手處理建立時間,以及更新時間。

接著我們要建立對應的model,form。

./symfony doctrine:build-all --classes-only

加上–classes-only是避免symfony直接將db table重建,造成資料遺失。

接著我們建立一個基本的CRUD吧。

./symfony doctrine:generate-module frontend member member

我們在frontend app底下建立一個member module。

接著Symfony會在app/frontend/modules/member底下建立好對應的actions以及templates。

我們看一下app/frontend/modules/member/templates/_form.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
<?php use_stylesheets_for_form($form) ?>
<?php use_javascripts_for_form($form) ?>
<form action="<?php echo url_for('member/'.($form->getObject()->isNew() ? 'create' : 'update').(!$form->getObject()->isNew() ? '?id='.$form->getObject()->getId() : '')) ?>" method="post" <?php $form->isMultipart() and print 'enctype="multipart/form-data" ' ?>>
<?php if (!$form->getObject()->isNew()): ?>
<input type="hidden" name="sf_method" value="put" />
<?php endif; ?>
  <table>
    <tfoot>
      <tr>
        <td colspan="2">
          <?php echo $form->renderHiddenFields(false) ?>
          &nbsp;<a href="<?php echo url_for('member/index') ?>">Back to list</a>
          <?php if (!$form->getObject()->isNew()): ?>
            &nbsp;<?php echo link_to('Delete', 'member/delete?id='.$form->getObject()->getId(), array('method' => 'delete', 'confirm' => 'Are you sure?')) ?>
          <?php endif; ?>
          <input type="submit" value="Save" />
        </td>
      </tr>
    </tfoot>
    <tbody>
      <?php echo $form->renderGlobalErrors() ?>
      <tr>
        <th><?php echo $form['name']->renderLabel() ?></th>
        <td>
          <?php echo $form['name']->renderError() ?>
          <?php echo $form['name'] ?>
        </td>
      </tr>
      <tr>
        <th><?php echo $form['email']->renderLabel() ?></th>
        <td>
          <?php echo $form['email']->renderError() ?>
          <?php echo $form['email'] ?>
        </td>
      </tr>
      <tr>
        <th><?php echo $form['phone']->renderLabel() ?></th>
        <td>
          <?php echo $form['phone']->renderError() ?>
          <?php echo $form['phone'] ?>
        </td>
      </tr>
      <tr>
        <th><?php echo $form['sex']->renderLabel() ?></th>
        <td>
          <?php echo $form['sex']->renderError() ?>
          <?php echo $form['sex'] ?>
        </td>
      </tr>
      <tr>
        <th><?php echo $form['created_at']->renderLabel() ?></th>
        <td>
          <?php echo $form['created_at']->renderError() ?>
          <?php echo $form['created_at'] ?>
        </td>
      </tr>
      <tr>
        <th><?php echo $form['updated_at']->renderLabel() ?></th>
        <td>
          <?php echo $form['updated_at']->renderError() ?>
          <?php echo $form['updated_at'] ?>
        </td>
      </tr>
    </tbody>
  </table>
</form>

這邊我們作點小修改,讓整個form更彈性一些。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<?php use_stylesheets_for_form($form) ?>
<?php use_javascripts_for_form($form) ?>
<form action="<?php echo url_for('member/'.($form->getObject()->isNew() ? 'create' : 'update').(!$form->getObject()->isNew() ? '?id='.$form->getObject()->getId() : '')) ?>" method="post" <?php $form->isMultipart() and print 'enctype="multipart/form-data" ' ?>>
<?php if (!$form->getObject()->isNew()): ?>
<input type="hidden" name="sf_method" value="put" />
<?php endif; ?>
  <table>
    <tfoot>
      <tr>
        <td colspan="2">
          <?php echo $form->renderHiddenFields(false) ?>
          &nbsp;<a href="<?php echo url_for('member/index') ?>">Back to list</a>
          <?php if (!$form->getObject()->isNew()): ?>
            &nbsp;<?php echo link_to('Delete', 'member/delete?id='.$form->getObject()->getId(), array('method' => 'delete', 'confirm' => 'Are you sure?')) ?>
          <?php endif; ?>
          <input type="submit" value="Save" />
        </td>
      </tr>
    </tfoot>
    <tbody>
      <?php echo $form?>
    </tbody>
  </table>
</form>

我們看一下長出來的CRUD是什麼樣子

http://web_site/member

我們點選一下New新增一筆資料

最底下的兩個欄位Created at以及Updated at,一個是資料的建立時間,一個是資料的更新時間。
這兩個欄位我們必須保護起來,不能讓使用者隨意的修改。
我們就來修改MemberForm
lib/form/doctrine/MemberForm.class.php

1
2
3
4
5
6
7
8
9
10
11
12
<?php
class MemberForm extends BaseMemberForm {
    protected function setupProtectField(){
        unset(  
            $this['created_at'],
            $this['updated_at']
        );
    }
    public function configure() {  
        $this->setupProtectField();
    }
}

把created_at,updated_at這兩個欄位unset掉。

我們再來看一下unset後的結果

可是性別欄位不是我們要的,必須改成radio button。
這時候我們就繼續修改MemberForm吧。
lib/form/doctrine/MemberForm.class.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<?php
class MemberForm extends BaseMemberForm {
    protected function setupProtectField(){
        unset(
            $this['created_at'],
            $this['updated_at']
        );
    }
 
    protected function setupWidgets(){
        $this->widgetSchema['sex'] = new sfWidgetFormChoice(array(
                    'multiple' => false,
                    'expanded' => true,
                    'choices' => array(
                        'm' => 'Male',
                        'f' => 'Female',
                    ),
                    'default' => 'm',
                ));        
    }
 
    public function configure() {
        $this->setupProtectField();
        $this->setupWidgets();
    }
}

這裡我們為sex欄位設定為sfWidgetFormChoice(選擇欄位)。

欄位屬性的設定值為
multiple => 是否可以多選
expanded => 是否展開選項

所以會有四種組合

multiple=false
expanded=false

multiple=true
expanded=false

multiple=false
expanded=true
 
 
multiple=true
expanded=true
 
 

我們就選擇radio button的格式。

我們頁面就會變成

接著我們得替每個欄位加上驗證。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
<?php
class MemberForm extends BaseMemberForm {
    protected function setupProtectField(){
        unset(
            $this['created_at'],
            $this['updated_at']
        );
    }
 
    protected function setupWidgets(){
        $this->widgetSchema['sex'] = new sfWidgetFormChoice(array(
                    'multiple' => false,
                    'expanded' => true,
                    'choices' => array(
                        'm' => 'Male',
                        'f' => 'Female',
                    ),
                    'default' => 'm',
                ));        
    }
 
    protected function setupValidator(){
        $this->validatorSchema['name'] = new sfValidatorString(
                        array(
                            'required' => true,
                            'max_length' => 10
                        )
        );
        $this->validatorSchema['email'] = new sfValidatorEmail(
                        array(
                            'required' => true,
                        )
        );
        $this->validatorSchema['sex'] = new sfValidatorChoice(
                        array(
                            'required' => true,
                            'multiple' => false,
                            'choices' => array('m', 'f'),
                        )
        );
        $this->validatorSchema['phone'] = new sfValidatorRegex(
                        array(
                            'required' => true,
                            'pattern' => '/^[0-9]{9,10}$/i',
                            'must_match' => true
                        )
        );
    }
 
    public function configure() {
        $this->setupProtectField();
        $this->setupWidgets();
    }
}

這邊我們加上了Validator欄位。
name => 不能超過10個字元,必填。
email => 必須符合email格式,必填。
sex    =>  必須是m or f 兩個選項其中一個,不得多選,必填。
phone => 使用regular expression,限制格式必須為數字,開頭是0,總長度為10位,必填。

我們來實驗一下結果。

透過這幾個簡單的設定,就可以達到自訂表單元件格式,以及欄位驗證的功能。

更詳細的一些使用方法可以參考官方文件的demo

http://www.symfony-project.org/jobeet/1_4/Doctrine/en/10

 

發表於 PHP, Symfony | 已標籤 , | 發表迴響

Symfony的Actions 和 Templates

在熟悉Symfony目錄規則後,就要開始進入Symfony的Actions 和 Templates了。

在預設的routing,url規則為
http://example.com/{Module}/{Action}/{Param1}/{Value1}/{Param2}/{Value2}?Param3=Value3

例如 http://example.com/blog/index/foo/1/bar/2?data=3 這個網址
程式的進入點為blog module 的index action。
app/frontend/modules/blog/actions/actions.class.php 繼續閱讀

發表於 PHP, Symfony | 發表迴響

Symfony的component

看完XDite寫的某篇有關Rails的MVP與Cells整理,不免想拿Symfony來作比較(純粹只是功能探討,沒有誰好誰壞的區別。)。

Symfony其實也提供類似的功能叫做Components。提供了一個mini controller協助我們把複雜的業務邏輯從view中抽離。 繼續閱讀

發表於 PHP, Symfony, 網頁開發 | 已標籤 , | 2 則迴響

撰寫時程控制的小技巧

作活動案最常碰到的狀況就是時程控管的問題。

例如8/1早上10點開放報名,一個禮拜後截止。

9/1號開始開放會員投票,兩個禮拜後,要停止投票。

隔兩天後要公佈票選的名單。

直覺的作法就是,在處理報名的頁面加上判斷。

1
2
3
4
5
<?php
    if ( time() < strtotime(REGIST_START_DATE) || time() > strtotime(REGIST_END_DATE) ) ){
           echo "尚未開放報名";
           die;
    }

票選的頁面,票選結果頁面也是如此,並透過config設定每個階段的開始以及結束時間。

隨著專案的進行,要測試每個階段的功能時,只好開始修改起始時間,去測試某個項目是否照著規劃進行。

但人的記憶是有限的。改到最後經常發生config忘了改回正確的起始時間,導致該開啟的沒開啟,不該開啟的項目卻提早開放了。(我就是那個記憶有限的人)

這時候就得開始砍掉重練重構了。

首先得把這些時程控制的項目抽出獨立出來。 繼續閱讀

發表於 PHP, 網頁開發 | 發表迴響

撰寫PHP應該避免的幾件事

最近翻閱前人寫的程式,覺得自己的壽命又少了好幾歲。

習慣不好的撰寫風格真的會害死人,尤其是後面接手的倒楣鬼。

以下是最近被毒害的幾個案例。 繼續閱讀

發表於 PHP, 網頁開發 | 2 則迴響

select資料order by random,又要分頁的解法

最近遇到一個需求,資料必須隨機排序,又得分頁,同時資料又不能重複出現在不同頁。

翻了翻舊程式碼,作法居然是。

select * from data order by rand();

把資料統統抓出快取起來再分頁。

如果資料量少還不打緊,資料一多時,光是處理這個超大的array就會瞬間吃光memory。

目前想到了一個解法。

在table中加入一個sortbyrand的int欄位。

要打亂排序時只需下一個簡單的sql。

例如資料總筆數不會超過10000

update some_table set sortbyrand=FLOOR(RAND()*10000);

要索引資料時,只需根據sortbyrand欄位排序就行了。

 

發表於 網頁開發 | 發表迴響