2014年5月11日 星期日

Android 開發 (四十三) facebook v2.0(3.14SDK) chat api

根據facebook v2.0的文件描述

The Chat/XMPP service and API, located at chat.facebook.com, will not be available once 1.0 is deprecated.

也就是說利用xmpp send message的功能,在明年的4/30將會失效,
然而facebook也有提供新的解法,也就是 message Dialog,
但是message Dialog必須安裝 Facebook Messager app 才能夠使用,
也就是說,假設使用者未安裝此APP,則呼叫該api 將會失效.....,
然而目前也還未看到其他的解決方法,這邊就先簡單的介紹一下 Message Dialog的用法,
FacebookDialog.MessageDialogBuilder builder = new FacebookDialog.MessageDialogBuilder(getActivity())
    .setLink("https://www.google.com")
    .setName("goo")
    .setCaption("it's a caption")
    .setPicture("image_url")
    .setDescription("some description")
    .setFragment(this);


if (builder.canPresent()) {
  builder.build().present()
}
使用方法非常簡單,只需使用MessageDialogBuilder,之後他會導向Facebook Messager app,
好處是我們不必再像以前以樣必須先建立xmpp的connection之後,
再利用intent service去send message,缺點是目前似乎沒看到background send message的方法,
只能希望facebbok之後會提供其他的api,讓我們可以更方便的傳送訊息了。

2014年5月10日 星期六

Android 開發 (四十二) facebook v2.0(3.14SDK) Scope app match to other app using Business api

最近 facebook 發佈了 sdk 3.14版本,其中最主要的修改是 facebook 在 4/30號之後註冊的app再也無法使用原本的api取得 friend list ,必須使用invitable api 來取得資料,並且限定app類型為GAME才能取得,取得的資料也有做一些變更,不過這部分不會在這裡多做說明(或許之後吧....),
以及新的app取得的fb_id不再是使用者真實的fb id ,而是scope  id,也就是說這個id僅可在我的app 裡面使用,也就是說同一個使用者在不同的app裡面擁有的fb id 不再相同。

假設我今天擁有多個app,我希望在多個app中確認該登入的使用者是否已經在我其他的app中登入過,我就必須使用 business api來map scope id。

在這裡我會稍微講解如何使用 Business api來 map Scope app,
要使用Business api 首先必須前往
設定 -> 進階

並完成註冊,只需要填一些基本資料即可,
如果卡在 You're not a Page Admin on any Facebook Pages.
他的意思是說,你必須在一個粉絲頁中擁有 Admin權限,最快的做法就是
只需要在自己的帳號中創建一個粉絲頁即可。

接著將我所有的app 加入我的business中

接著再使用facebook 範例的sample code
new Request(
    session,
    "/me/ids_for_business",
    null,
    HttpMethod.GET,
    new Request.Callback() {
        public void onCompleted(Response response) {
            /* handle the result */
        }
    }
).executeAsync();
就會取得相關的資料了,以下是我這邊取得的結果

  1. {
    • "id":"149102310",
    • "app":{
      • "id":"test1_fb_id",
      • "namespace":"test1",
      • "name":"test1"
      }
    },
  2. {
    • "id":"10202864123396999",
    • "app":{
      • "id":"test2_fb_id",
      • "name":"test2"
      }
    }
其中149102310為app test1 的scope app id ,10202864123396999 為app test2的scope app id,
這樣我們就可以match到該使用者是否有在我其他的app中使用facebook登入過了。


附註:
我們也可以使用 "/{friend_id}/ids_for_business" 來做朋友Id的mapping,這個功能目前是可用,
希望過陣子還是可以使用(極度不信任facebook api XD)

2014年5月3日 星期六

Android 開發(四十一) Spotlight

這是由 Romain Guy 提供的作法
下面是他的網址
http://www.curious-creature.org/2012/12/13/android-recipe-2-fun-with-shaders/

他的做法其實是利用 canvas  draw bitmap 以及 paint來完成,
首先他將背景設置到 paint,
接著將mask 轉成 alpha 接著在canvas上繪製就完成了。

知道大概要怎麼做之後,我們來看code

  • 將背景設置到 paint

 private void createShader() {
  View target = getRootView().findViewById(mTargetId);
  mTargetBitmap = createBitmap(target);
  Shader targetShader = createShader(mTargetBitmap);
  mPaint.setShader(targetShader);
 }

 private static Shader createShader(Bitmap b) {
  return new BitmapShader(b, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
 }

 private static Bitmap createBitmap(View target) {
  Bitmap b = Bitmap.createBitmap(target.getWidth(), target.getHeight(), Bitmap.Config.ARGB_8888);
  Canvas c = new Canvas(b);
  target.draw(c);
  return b;
 }


  • 接著將mask圖轉成alpha


        mMask = convertToAlphaMask(BitmapFactory.decodeResource(getResources(), maskId));
 
        private static Bitmap convertToAlphaMask(Bitmap b) {
  Bitmap a = Bitmap.createBitmap(b.getWidth(), b.getHeight(), Bitmap.Config.ALPHA_8);
  Canvas c = new Canvas(a);
  c.drawBitmap(b, 0.0f, 0.0f, null);
  return a;
 }


  • 接著在canvas上繪製

canvas.drawBitmap(mMask, 0.0f, 0.0f, mPaint);

基本的功能這樣就完成了,如下圖



接著該如何讓spotlight移動以及放大是另一個問題,
假設我們希望將剛剛的spotlight放大3倍,並且移至較中間的位置 100,100 我們應該怎麼做呢?

  mShaderMatrix.setScale(1.0f / 3, 1.0f / 3);
  mShaderMatrix.preTranslate(-100, -100);

  mPaint.getShader().setLocalMatrix(mShaderMatrix);
  
  canvas.translate(100, 100);
  canvas.scale(3, 3);
  canvas.drawBitmap(mMask, 0.0f, 0.0f, mPaint);

可以發現,作者的做法是

  1. 先將背景先縮小並反向位移
  2. 接著再將整個畫布放大並位移


也就是原先看到的mask 被放大的並位移到較中間的位置,而背景的縮放以及位移被抵銷掉
結果的圖如下


由於作者的範例包含太多動畫,所以我寫了一個更簡單的sample,由兩個按鈕來控制
postion以及 scale  ,讓大家比較容易trace,附上 sample code

2014年4月26日 星期六

Android 開發 (四十) FloatingLabelLayout

FloatingLabelLayout
 https://gist.github.com/chrisbanes/11247418

這是ChrisBanes公布的source code 先看一下功能


我們可以稍微看一下source code,

FloatLableLayout.java


  
    @Override
    public final void addView(View child, int index, ViewGroup.LayoutParams params) {
        if (child instanceof EditText) {
            // If we already have an EditText, throw an exception
            if (mEditText != null) {
                throw new IllegalArgumentException("We already have an EditText, can only have one");
            }

            // Update the layout params so that the EditText is at the bottom, with enough top
            // margin to show the label
            final LayoutParams lp = new LayoutParams(params);
            lp.gravity = Gravity.BOTTOM;
            lp.topMargin = (int) mLabel.getTextSize();
            params = lp;

            setEditText((EditText) child);
        }

        // Carry on adding the View...
        super.addView(child, index, params);
    }
private void setEditText(EditText editText) {
        mEditText = editText;

        // Add a TextWatcher so that we know when the text input has changed
        mEditText.addTextChangedListener(new TextWatcher() {

            @Override
            public void afterTextChanged(Editable s) {
                if (TextUtils.isEmpty(s)) {
                    // The text is empty, so hide the label if it is visible
                    if (mLabel.getVisibility() == View.VISIBLE) {
                        hideLabel();
                    }
                } else {
                    // The text is not empty, so show the label if it is not visible
                    if (mLabel.getVisibility() != View.VISIBLE) {
                        showLabel();
                    }
                }
            }

            @Override
            public void beforeTextChanged(CharSequence s, int start, int count, int after) {
            }

            @Override
            public void onTextChanged(CharSequence s, int start, int before, int count) {
            }

        });

可以看到他在 addview時將 child的 edittext加上  textchanged Event
然後再利用textChanged event去顯示或隱藏相關的label


其實我比較好奇的地方是他為何不乾脆將 editText 直接包含在custom view裡頭
反而是利用如下圖的方式


    <com.example.my.FloatLabelLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="16dp"
        app:floatLabelTextAppearance="@style/TextAppearance.YourApp.FloatLabel" >

        <EditText
            android:id="@+id/edit_password"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:hint="password"
            android:imeOptions="actionDone"
            android:inputType="textNoSuggestions"
            android:singleLine="true" />

    </com.example.my.FloatLabelLayout>

是說這樣寫的自由度比較高,但是使用者錯誤使用的機會也會增加,可以想像的好處大概只有當 FloatLabelLayout 的child 有較多UI時,可以將layout 都放在 floatLabelLayout內可以少使用一個
relative layout 如下


    <com.example.my.FloatLabelLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="16dp"
        app:floatLabelTextAppearance="@style/TextAppearance.YourApp.FloatLabel" >

        <EditText
            android:id="@+id/edit_password"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:hint="password"
            android:imeOptions="actionDone"
            android:inputType="textNoSuggestions"
            android:singleLine="true" />

        <ImageView android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:src="@drawable/ic_launcher"/>
    </com.example.my.FloatLabelLayout>


但是就如上面所說的,當使用者加入兩個editText將會造成錯誤的情況,我目前能想到比較好的解法應該是將view綁定TextView而不是FrameLayout,
簡單的說就是客製一個TextView 並包含上面的功能。


小結

這算是個簡單卻頗有趣的UI。

2014年4月23日 星期三

設計模式(十二) Bridge 模式

Bridge 模式 :  將抽象與實作解耦合,使他們都可以獨立的變化。

解耦: 讓事物獨立地行事,或者至少明確的宣告之間的關係
抽象:不同事物之間概念上的關聯方式


Bridge模式遵循兩個原則:

  1.  找出變化並封裝之
  2. 優先使用物件聚合而不是類別繼承

舉個例子:
假設我有兩個版本的繪圖程式DP1和DP2然後我希望用這兩個繪圖程式來繪製圖形
方形和圓形,範例如下圖



依照需求來看,我們可能會畫出類似上面的UML圖,
接著開始思考該如何將圖組合在一起,

首先我們從Client使用物件的順序來思考,假設Client需要畫一個V1Rectangle,
依序會呼叫 Client -> rectangle.draw -> dp1.drawline *4 
我們發現雖然類別圖的物件非常的多,但是實際上我們只須處理三個物件
  1. 使用矩形的Client物件
  2. V1Rectangle物件
  3. DP1物件
接著我們可能會畫出如下的UML圖


利用繼承來解決這個需求似乎是很自然的解法,但是再仔細思考一下,如果現在新增了一種形狀和一種繪圖程式,物件的數量會變成9個,如果將繪圖程式持續增加,將會造成物件爆炸,
這讓我們必須重頭思考這樣的設計是否是有問題的。

過度使用繼承
過度使用繼承透過特殊化處理變化,從已有的類別衍生新類別,會造成物件越來越難以維護
用設計模式進行思考,用物件的職責來思考而不是其結構,良好的設計應該為每種變化使用不同的特殊化(繼承)並將變化轉移到使用或擁有這種變化的物件中(組合)

那我們應該怎麼做呢?

我們應該
  1. 找出變化並封裝之
  2. 優先使用物件聚合,而不是類別繼承
例如 Client應該只知道他在使用一個Shap物件而不應該知道是Rectangle或Circle,
Shap應該只知道目前是使用某一個版本的Drawing繪圖程式而不必知道是V1或是V2

再看一次下面的圖


Client只知道Shap 所以我們可以思考出左邊的類別圖,Shap應該只知道Drawing
所以我們會得到右邊的圖,接著兩張圖該怎麼聯結再一起呢?

這是一個抽象與實作分離的例子,形狀的抽象與繪圖實作分離,這其實就是Bridge模式。

讓我們來看一下Bridge模式的特徵

意圖:將一組實作與另一組使用他們的物件分離

問題:一個抽象類別的衍生類別必須使用多個實作,但不能出現類別數量爆炸性增長。

參與者與協作者:Abstraction為要實作的物件定義介面,Implementor為具體的實作類別定義介面
Abstraction的衍生類別使用Implementor的衍生類別,卻無須知道自己具體使用哪一個ConcreteImplementor

解決方案:為所有實作定義一個介面,供抽象類別的所有衍生類別使用。

效果:實作與使用實作的物件解耦,提供了可擴展性,客戶物件無須操心實作問題。

實作:
  • 將實作封裝在一個抽象類別。
  • 在要實作的抽象基礎類別中包含一個實作(也可利用介面代替抽象類別)。


小結:

Bridge模式把握幾個原則

物件對自己負責: Shap可以繪製自己,Drawing負責繪圖元素

抽象類別:Shap代表形狀,Drawing代表繪圖程式

透過抽象類別進行封裝:Client只看的到Shap , Shap只知道Drawing 

一條規則,實作一次:  抽象類別中經常有些方法實際使用實作物件,抽象類別的子類別會忽叫這些方法,如此再需要修改時,只需要修改一個地方。

可測試性:改進的方案,讓我們可以很輕易的做測試,可分別對shap以及drawing做測試。

2014年4月13日 星期日

設計模式 (十一) 開拓視野 part2

在開拓視野 part1 提出了物件,封裝,繼承的一些不同看法,在part2會針對聚合,共通性和可變性分析與抽象類別,以及敏捷程式設計的品質做相關的介紹。

在part1我們可以瞭解到特殊化繼承帶來的便利性,以及之後所造成的麻煩,為了避免這個問題,我們可以使用聚合的方式將需要的功能帶入,卻又可以保持內聚性以及封裝性,

舉個例子
現在有個需求

  • 每種動物都有數量不同的腿
  • 動物物件必須能夠記住並獲取這一資訊
  • 每種動物的移動方式都不同
  • 對於指定的地形類型,動物物件必須能夠計算出往返的時間
接著該怎麼實作呢?
我們發現每種動物的移動方式是不同的,有些動物是會飛,有些動物會跑,有些動物會游泳,我們總不能為了每種動物都使用一個method吧,但是我們發現他們的都有移動的行為,我們發現了變化(移動方式),同時我們也發現了相同的概念(移動的行為),所以我們可以將變化的部分封裝起來,在Design pattern中有提到

考慮設計中哪些地方可能有變化,如何在不重新設計的情況下增加需求因應變化,關鍵在於封裝變化的概念。

經過封裝變化之後我們的uml 會如下所示

這麼做的好處在於,當有新的behavior出現,只需要增加一個class即可,並不會重新設計。

注意到上面在分析如何封裝時其實有一個分析的方式叫做共通性和可變性分析和抽象類別,
共通性分析就是尋找一些共同的要素,例如移動的行為,可變性分析則說明了物件之間不同的行為,例如飛,走,游。

在設計模式的解析與活用中提到

共通性分析尋找的是不可能隨時間而改變的結構,而可變性分析則要找到可能變化的結構,
從架構的視角來看,共通性分析為架構提供長效的要素,而可變性分析則促進適應實際使用所需。



從上面這張圖我們可以看出這樣的概念

  • 抽象類別(共通性):需要用甚麼介面來處理這個類別的所有責任
  • 衍生類別(可變性):對於這個指定的實作(這個變化),應該怎樣根據指定的規約來實作他

下面有關於使用抽象類別進行特殊化的好處,也就是繼承抽象的好處



我們發現這種設計模式,總是預先設計大概的雛形,這是否跟敏捷開發中不預先設想的想法衝突?

關於這個問題我們再進一步仔細思考,我認為在某些情況下我們的確不該預先設想,過於關注細節,我們應該觀察物件發生變化的可能,並且使用封裝的方式將變化封裝,然而我們必須注意的是,敏捷開發以及設計模式所關注的共同要點
  1. 無冗餘
  2. 可讀性
  3. 可測試性
無冗餘性

Kent Back Once and Only Once rule,他說明了功能只在一個地方實作,也就是同樣的程式碼不出現在第二個地方,避免出現重複的程式碼,消除重覆的好處在於,當功能需要更改時我們只需要更改一個地方,而且在更改時我們不需再去考慮是否有遺漏。
這種概念就跟依介面設計的概念不謀而合,找出變化,利用介面使程式碼高度內聚。

可讀性

也就是反應意圖,這如同依介面設計的概念,當我們使用介面時,我們就是利用了介面來傳遞我們的概念,例如  上面動物行為的例子中  behavior.move(),代表著動物執行了移動的動作
但是我們並不在乎動物是如何移動的,它反映了動物的意圖。

可測試性

TDD與依介面設計的概念也是一樣的,如果封裝的好,就會產生高內聚,鬆耦合的程式碼,
也會讓我們在寫測試的時候更加的方便。

其實敏捷開發與設計模式要求的都是

內聚: 程式碼更容易測試,因為程式碼只負責一個責任(SRP)
鬆耦合:由於需要關注的東西較少,所以在測試的時候不需要去在乎太多其他類別的東西
冗餘程式碼:冗餘程式碼過多會造成測試涵蓋率下降
可讀性好:可以更明確的表達意圖
封裝性好:鬆耦合,減少對其他物件的關注

小結

其實不管是設計模式或是敏捷開發,其實大家在乎的要點都是,如何開發出高品質的程式碼,
簡單的說,想辦法寫出高內聚,鬆耦合,封裝變化的程式碼就對了

Robolectric介紹(二) 如何使用Robolectric 開發 project with SherlockActionBar

在 Robolectric介紹(一) shadow 介紹完簡單的功能之後,我開始思考是否有辦法將這個方法套用在我們的Project上面,在設定完成之後我嘗試run了一次test,結果如下


java.lang.IllegalStateException: there must have been some overlap for resourceIdToResName! expected 5815 but got 5814
at org.robolectric.res.MergedResourceIndex.merge(MergedResourceIndex.java:25)
at org.robolectric.res.MergedResourceIndex.(MergedResourceIndex.java:17)
at org.robolectric.res.RoutingResourceLoader.(RoutingResourceLoader.java:22)
at org.robolectric.RobolectricTestRunner.createAppResourceLoader(RobolectricTestRunner.java:598)
at org.robolectric.RobolectricTestRunner.getAppResourceLoader(RobolectricTestRunner.java:582)
at org.robolectric.internal.ParallelUniverse.setUpApplicationState(ParallelUniverse.java:98)
at org.robolectric.RobolectricTestRunner.setUpApplicationState(RobolectricTestRunner.java:401)
at org.robolectric.RobolectricTestRunner$2.evaluate(RobolectricTestRunner.java:219)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:263)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:68)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:47)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:231)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:60)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:229)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:50)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:222)
at org.robolectric.RobolectricTestRunner$1.evaluate(RobolectricTestRunner.java:174)
at org.junit.runners.ParentRunner.run(ParentRunner.java:300)
at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:50)
at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:467)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:683)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:390)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:197)

在我花了很久時間debug與search之後,發現在xml內的小差異會造成test fail android:id="@android:id 以及 android:id="@+id 
只有android要求必須使用@android:id的才使用這種設定方式 例如 tabhost,ListFragment
否則一律使用 android:id="@+id ,錯用這個設定會造成Robolectric 無法 test

在projct成功可以test之後我遇到了另一個問題,由於我們的project implement了
ActionBarSherlock 所以在測試的時候常常會看到一些關於actionbar設定的nullpointer
為了解決這個問題,就必須在測試前預先做一些相關的設定

ActionBarSherlockRobolectric 提供了相關的解法,由於他提供的解法是給舊版本的,所以在這邊我們必須做一些小修改,只需要將

shadowOf(mActivity).setContentView(contentView);

修改成

mActivity.getWindow().setContentView(contentView);

就完成了 

接著我就可以開始做相關的測試 例如我希望測試從mainactivity 導向某個fragment之後的測試 
我可以這樣寫
 @Before
 public void init(){
  ActionBarSherlock.registerImplementation(ActionBarSherlockRobolectric.class);
  ActionBarSherlock.unregisterImplementation(ActionBarSherlockNative.class);
  ActionBarSherlock.unregisterImplementation(ActionBarSherlockCompat.class);
  
 }
 @Test @Config(reportSdk = 10, manifest = "AndroidManifest.xml")
    public void shouldNotBeNull() throws Exception {
  
     ShadowApplication shadowApplication = Robolectric.shadowOf(Robolectric.application);
     shadowApplication.declareActionUnbindable("com.google.android.gms.analytics.service.START");
     Fragment myFragment = new myTestFragment();
       
     
        startFragment( myFragment );

       assertNotNull( myFragment );
    }
    
    public static void startFragment( Fragment fragment )
    {

     SherlockFragmentActivity activity = Robolectric.buildActivity( MainActivity.class )
                                               .create()
                                               .start()
                                               .resume()
                                               .get();   

        FragmentManager fragmentManager = activity.getSupportFragmentManager();
        FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
        fragmentTransaction.add( fragment, null );
        fragmentTransaction.commit();
    }

需要注意的是,我在init做了相關的設定之後,接著在test的地方做了一些相關的Config,
AndroidManifest是要測試project的AndroidManifest.xml 否則會出現resourceNameNotFound
由於我們的project有使用google trace analytics所以必須加入下面這行
shadowApplication.declareActionUnbindable("com.google.android.gms.analytics.service.START");

否則會出現
java.lang.NullPointerException
 at com.google.analytics.tracking.android.AnalyticsGmsCoreClient$AnalyticsServiceConnection.onServiceConnected(AnalyticsGmsCoreClient.java:176)
 at org.robolectric.shadows.ShadowApplication$2.run(ShadowApplication.java:246)
 at org.robolectric.util.Scheduler$PostedRunnable.run(Scheduler.java:162)
 at org.robolectric.util.Scheduler.runOneTask(Scheduler.java:107)
 at org.robolectric.util.Scheduler.advanceTo(Scheduler.java:92)
 at org.robolectric.util.Scheduler.advanceToLastPostedRunnable(Scheduler.java:68)
 at org.robolectric.util.Scheduler.unPause(Scheduler.java:25)
 at org.robolectric.shadows.ShadowLooper.unPause(ShadowLooper.java:220)
 at org.robolectric.shadows.ShadowLooper.runPaused(ShadowLooper.java:259)
 at org.robolectric.util.ActivityController.create(ActivityController.java:111)
 at org.robolectric.util.ActivityController.create(ActivityController.java:123)
 at MyFragmentTest.startFragment(MyFragmentTest.java:61)
 at MyFragmentTest.shouldNotBeNull(MyFragmentTest.java:50)
 at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
 at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
 at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
 at java.lang.reflect.Method.invoke(Unknown Source)
 at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:45)
 at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:15)
 at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:42)
 at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:20)
 at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:28)
 at org.robolectric.RobolectricTestRunner$2.evaluate(RobolectricTestRunner.java:233)
 at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:263)
 at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:68)
 at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:47)
 at org.junit.runners.ParentRunner$3.run(ParentRunner.java:231)
 at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:60)
 at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:229)
 at org.junit.runners.ParentRunner.access$000(ParentRunner.java:50)
 at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:222)
 at org.robolectric.RobolectricTestRunner$1.evaluate(RobolectricTestRunner.java:174)
 at org.junit.runners.ParentRunner.run(ParentRunner.java:300)
 at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:50)
 at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
 at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:467)
 at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:683)
 at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:390)
 at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:197)


接著myTestFragment 其實是使用mock的原理,由於我測試的Fragment在init的時候會去
onCreateOptionsMenu ,並且執行 mItem.getActionView(), 並且做一些相關的設定
但是我們的actionbar是用mock出來的所以在getActionView的時候會回傳null,
由於Menu的相關功能我可以額外寫一個測試,所以在這裡我並不想測試這部分也不想關心這部分,所以我直接mock了我想測試的fragment並且override了onCreateOptionsMenu的method,範例如下

public class myTestFragment extends MyFragment{

 @Override
 public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
  // TODO Auto-generated method stub
  //super.onCreateOptionsMenu(menu, inflater);
 }
}

在相關的設定都設定完成之後,我run了一次test結果卻依舊是紅燈


android.view.InflateException: <merge /> can be used only with a valid ViewGroup root and attachToRoot=true
 at android.view.LayoutInflater.inflate(LayoutInflater.java:455)
 at android.view.LayoutInflater.inflate(LayoutInflater.java:396)
 at android.view.LayoutInflater.inflate(LayoutInflater.java:352)
 at ActionBarSherlockRobolectric.setContentView(ActionBarSherlockRobolectric.java:40)
 at com.actionbarsherlock.app.SherlockFragmentActivity.setContentView(SherlockFragmentActivity.java:261)
 at com.nineyi.MainActivity.onCreate(MainActivity.java:268)
 at android.app.Activity.performCreate(Activity.java:5104)
 at org.fest.reflect.method.Invoker.invoke(Invoker.java:112)
 at org.robolectric.util.ActivityController$1.run(ActivityController.java:116)
 at org.robolectric.shadows.ShadowLooper.runPaused(ShadowLooper.java:257)
 at org.robolectric.util.ActivityController.create(ActivityController.java:111)
 at org.robolectric.util.ActivityController.create(ActivityController.java:123)
 at MyFragmentTest.startFragment(MyFragmentTest.java:61)
 at MyFragmentTest.shouldNotBeNull(MyFragmentTest.java:50)
 at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:45)
 at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:15)
 at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:42)
 at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:20)
 at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:28)
 at org.robolectric.RobolectricTestRunner$2.evaluate(RobolectricTestRunner.java:233)
 at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:263)
 at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:68)
 at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:47)
 at org.junit.runners.ParentRunner$3.run(ParentRunner.java:231)
 at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:60)
 at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:229)
 at org.junit.runners.ParentRunner.access$000(ParentRunner.java:50)
 at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:222)
 at org.robolectric.RobolectricTestRunner$1.evaluate(RobolectricTestRunner.java:174)
 at org.junit.runners.ParentRunner.run(ParentRunner.java:300)
 at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:50)
 at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
 at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:467)
 at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:683)
 at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:390)
 at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:197)


這個地方我目前還想不到解法,原因是在MainActivity使用了<merge> 當作我們的root,造成了他的錯誤,由於目前還找不到特別的解法,所以目前這部分我也只能先將source改成 releativeLayout,希望之後有其他的方法可以解決這個問題

在經過了一番折騰努力之後終於看到了期待已久的綠燈...
不過這只不過是個開始,測試的路才剛開通而已~


相關參考文件
https://gist.github.com/JakeWharton/3803294
https://github.com/playhaven/playhaven-robolectric
http://robolectric.blogspot.mx/2013/05/configuring-robolectric-20.html
http://robolectric.blogspot.tw/2013/04/the-test-lifecycle-in-20.html
http://stackoverflow.com/questions/17993239/getsupportactionbar-returns-null-with-robolectric/18034473#18034473