2016年2月22日 星期一

Android 開發 (112) Cloud Test Lab

相信有在做測試的大家常常會遇到一個問題,測試跑在自己的機台上,好花時間而且我的機台又不夠多,更不用說android 4.4 5.0 6.0 版本的問題了

現在google 提供了一個solution,你只需要上傳你的測試apk 點選你想要的device 和version,然後按下測試,接著去泡杯咖啡,之後report就會自動產生出來囉!!

先看一下該如何設定

首先必須先將 android studio 升級到1.3+以上

接著前往test case 設定的地方,點選Target 到 Cloud Test Lab Device Matrix
然後在Matrix configuation 可以設定想測試的device / sdk version / 直版橫版
記得第一次設定的時候必須連結到google 帳號(該帳號必須啟用付費專案),才可以使用
(目前cloud test Lab 是freetrialing所以不用擔心charge的問題XD)

接著設定完成後直接點擊平常測試的那個Run鍵一切就開始了....
接著可以在logcat裡面看到相關訊息

點擊該連結就可以看到網頁版,以下是測試結果(可以看到我的測項都fail了 Orz..)



當然,除了使用android studio外,你也可以使用網頁版
直接使用google developer帳號,search Cloud Test Lab
就可以找到相關使用功能

網頁版的操作也很簡單,你只需要上傳apk接著點擊測試就完成了,如下


對於我們這種device 數以及sdk 數相對不足的開發者
cloud test 真的很方便啊,幫小弟省了很多$$ ,不知道之後的價錢會是怎樣,
不過目前是免費,大家就盡量嘗試吧!!

2016年1月24日 星期日

Android 開發 (111) Android 開發(111) Recyclerview addOnItemTouchListener 應用

用過reyclerview的大家應該都覺得沒有onitemclick這件事情讓人非常麻煩,
大部分的人都是使用onclicklistener直接在viewholder裡面去實作,不過這真的很困擾,
因為click的動作就得跟viewholder綁在一起
寫法大概如下
public class LineViewHolder extends RecyclerView.ViewHolder {
private final TextView textView;

public LineViewHolder(View itemView) {
    super(itemView);
    textView = (TextView)   itemView.findViewById(R.id.info_text);
}

public void bindview(int pos) {
    textView.setText("pos " + pos);
    itemView.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View view) {

        }
    });
}
}
另一部份的人應該都是將onclicklistener直接從adapter 一路往下帶
寫法大概如下
public class LineViewHolder extends RecyclerView.ViewHolder {
private final TextView textView;
private OnClickListener mListener;

public LineViewHolder(View itemView, OnClickListener listener) {
    super(itemView);
    mListener = listener;
    textView = (TextView) itemView.findViewById(R.id.info_text);
}

public void bindview(int pos) {
    textView.setText("pos " + pos);
    itemView.setOnClickListener(mListener);
}
}
最近看到一個有趣的寫法,在這邊分享給大家
他使用了addOnItemTouchListener,特別之處在於他的寫法有點像是
listview的setOnItemclick ,不再需要將listener一路往下帶到viewholder或是寫在viewholder裡讓其他人都找不到了XD
    mDetector = new GestureDetectorCompat(this, new RecyclerViewOnGestureListener(new View.OnClickListener() {
        @Override
        public void onClick(View view) {
            Log.d("Ted","onclick");
        }
    }));

    mRecyclerview.addOnItemTouchListener(new RecyclerView.OnItemTouchListener() {
        @Override
        public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e) {
            mDetector.onTouchEvent(e);
            return false;
        }

        @Override
        public void onTouchEvent(RecyclerView rv, MotionEvent e) {

        }

        @Override
        public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {

        }
    });

private class RecyclerViewOnGestureListener extends GestureDetector.SimpleOnGestureListener {
    private View.OnClickListener mOnclick;
    public RecyclerViewOnGestureListener(View.OnClickListener onClickListener){
        mOnclick = onClickListener;
    }
    @Override
    public boolean onSingleTapConfirmed(MotionEvent e) {
        View view = mRecyclerview.findChildViewUnder(e.getX(), e.getY());
        int position = mRecyclerview.getChildPosition(view);

        // handle single tap
        if(mOnclick!= null){
            mOnclick.onClick(view);
        }

        return super.onSingleTapConfirmed(e);
    }
}
不過,目前看到這樣的寫法還是會有問題,由於是使用touchevent並不是真的click行為,所以現實上還是有差距的,如果真的要做到一樣的click的話,就必須將view裡面的touch行為複製出來實作了….
不過如果只是單純的singleTap行為,就可以利用這種快速的做法來完成囉~

2016年1月11日 星期一

Android 開發(110) dagger 實作概念

最近由於在研究架構方面的程式,所以又把dagger 的code拿出來讀了一遍,
今天就稍微解釋一下dagger幫我們省略掉的那些步驟吧!

首先,用過dagger的人都會看到類似這樣的code



getComponent().inject(this)

然後上方的 mainpresent就莫名的創建好了,所以立刻拿來用.

這麼神奇的code到底是怎麼做到的?

其實看完他的程式碼之後就會發現,他其實使用了ioc的原理,
將MainActivity 塞到 創建出來的code之後直接access field


從code flow來看,當上面的程式碼call inject之後就會call 到
 mainActivityMembersInjector.injectMembers();
而mainActivityMembersInjector 是誰?

可以稍微往上看一下
你可以想像  mainActivityMembersInjector可以取得所有MainActivity需要被注入的class
例如presenter

DaggerMainComponent.java



從下面這張圖就可以看出,
instance.mainPrsenter = mainPrsenterProvider.get(); 就是將 presenter init的地方
所以當MainActivity call 了 inject  之後
presenter就被init了

MainActivity_MembersInjector.java




到這邊,其實我對於中間那層ioc(DaggerMainComponent)有點疑問,如果只是要mock presenter
其實不需要中間這層(DaggerMainComponent),我只需要將presenterProvider mock掉就行了
也就是

架構也可以換成在DaggerMainComponent直接改成

public void inject(){
     presenter = MockPresenterProvider.getPresenter();
}

然後provider換成mock的provider就行了.

目前要mock的方法,看起來都必須使用flavor 然後實作相同的回傳值,
個人覺得看似很方便,但是當refactor or rename的時候很危險,除非有用interface 限制住,否則很容易會造成code 無法build 過...


最近一直在思考,到底要不要將舊project翻過一輪...
有很多好處,但是翻過一輪也很痛苦啊!!
想用dagger又覺得這樣改似乎有點可怕,哈哈

希望大家不要遇到跟我一樣的情況,能重新開始一個project真的是很開心的一件事情
希望我也能夠有重新開始的時候啊(遠望..)

2016年1月7日 星期四

Android 開發(一百零九) Robotium 開發

最近在研究自動測試相關的工具,恰巧看到了一個不錯的工具

Robotium

http://robotium.com/products/robotium-recorder

他的目標就是... 我們不需要會寫test case 我們只需要知道怎麼錄test case 就可以了
錄完之後test case 自動就會幫你產生code,多麼的方便啊!!

下面來看一下實際運作方式
(由於牽涉公司的程式碼,所以不能夠給大家看到class name部份)
可以看到隨著操作就可以產生步驟,之後按下save就自動生成code了
真的很方便



產生完test case 之後可以利用程式跑一下測試,下面是測試的結果




結論:

robotium的確是很方便的工具,可以快速產生測試程式碼,
不過我稍微玩了一下發現他幾個缺點,

判斷測試結果是否正確這段程式碼,可能還是要手動寫,雖然robotium提供點擊view就可以順便幫你判斷,但是有時候view剛好不能點,他就偵測不到....

點擊view的時候,由於test case 是自動產生的,有時候會點錯view ,例如他是抓整個畫面裡的ImageView 結果整個畫面有十個imageView ,他就會抓錯,所以在產生完code之後,還是要稍微試一下看看有沒有正確執行才行,小部分可能需要做修改,不過整體上還算方便的
例如剛剛的範例

//Click on Empty Text Viewsolo.clickOnView(solo.getView("xxx_searchview"));//Click on 150304solo.clickInList(1, 0);

還會幫你產生註解唷~ 有興趣的大家可以找時間試一下

2016年1月3日 星期日

Android 開發 (一百零八) MVP 概念

前陣子聽了幾個高手的影片,讓我又重新開始看MVP這個程式架構,
然後這個禮拜花了點時間將這個功能實作了一下,

先說明一下MVP

MVP 就是 model - View - Presenter

通常的架構分法會是,將api 的資料分成一個class 然後presenter負責邏輯
而view 就是負責ui 的呈現

舉個簡單的例子

當點擊加入購物車的時候,會發送一個api request ,然後這時候ui 會呈現一個progressing 的樣式,當api request 回來並且是加入成功的狀態,則將按鈕狀態改為已經加入購物車

以這個範例來說

progressing 樣子 and 按鈕的文字顯示都會在view (通常都會在activity or fragment)

而 api request 通常會是在model

presenter 則是中間溝通的層級

程式的話,

在view 那層應該會看到類似這樣的code

void onShowProgressBar()
void onHideProgressBar()
void onAPIRequestSuccess()
void onAPIRequestFailed()

然後在presenter的話會有類似這樣的code

void doAddToShoppingCart()

然後在model那層
boolean addShoppingCart();

model那層關心的是  如何將加入購物車api 的資料傳回來
presenter那層關心的是 取回api資料之後要判斷為加入成功or 失敗,並且要在何時顯示progressbar 和隱藏progressbar的這個動作

view 那層關心的是  當被呼叫隱藏progressbar的時候,要實際去做 progressbar.setvisibility(View.Gone)
或者當api 成功被呼叫的時候,按鈕狀態要改為已經加入購物車的動作

所以要做相關的測試時
我們可以

測試view 利用 espresso 來測試 ui 呈現方式, mock presenter 讓他call onAPIReuqestSuccess or onAPIRequestFailed 來看看 ui 呈現的方式是否跟預期一樣

測試 presenter 利用 unit test 來測試 邏輯,mock api and view 當api 回覆加入購物車成功時,presenter是否也call 到 onAPIRequestSuccess

測試 model 利用 unit test 來測試 api ,實際去打api 或mock api 結果,判斷回傳的資料是否跟預期一樣

MVP的架構的確讓我們可以擁有很漂亮的架構,而且testable,但是他帶來的副作用就是龐大的架構,大量的class ,以及大量的interface

目前我還無法說服自己以及他人使用這個架構,縱使他有很多優點,但是增加開發成本這件事讓我思考了許久,或許未來可以想到更好的解決方案吧.

對了,這次沒有sample code 只有google 提供的 test sample
https://github.com/googlecodelabs/android-testing

有興趣的人歡迎來討論一下,大家用了MVP是否都跟預期的一樣美好?

2015年12月9日 星期三

Android 開發(一百零七) Android Studio 2.0 hotkey 筆記



 .settext -> .setColor  可以用tab不要用enter


Bitmap b =  null;
想要看更多可能的提示  control + shift + space 可能可以看到更多有用的提示


alt + or   可以協助選取


alt + enter 可以將constructor直接建立field 並且sign




也可以直接將instanceof 直接轉型




fori =>  live template
list.fori  => for(int i = 0; i< list.size() ; i++)

logi => Log.i(TAG, "liveTemplate: 123");
lost => private static final String TAG = "MainActivity";
MainActivity 是依照class name

logm =>  method 參數印log 

public void send(String p1, String p2){
    Log.d(TAG, "send() called with: " + "p1 = [" + p1 + "], p2 = [" + p2 + "]");
}

logr => return value 印出來
wtf => Log.wtf(TAG,"msg",new Exception);

command + shift + A => action name 快捷鍵提示視窗
選擇 replace structure  可以使用regex架構的 replace

shrinkResource = true
可以減少resource的數量,但是會減慢app的build速度

其他快捷鍵
Find symbol: OPT+CMD+O
Find Commands : Shift + CMD + A
View implementation of symbol: CMD+B
View implementation : OPT+CMD+B
recently used files: CRT+TAB


debug:
Evaluate 快速debug



condition debug break 例如recycler view你不會想針對每個一個一個看,你只會想要看某一個的時候,例如可以針對position==3
condition debug -> more info 可以做到增加log
可以直接在debug - >  console裡面看到,記得要將Suspend的勾勾取消選取



Android 開發(一百零六) 利用retrofit & gradle flavor來建立測試環境

retrofit 是個非常方便開發的library,利用它我們可以快速開發,而且快速測試,但是該怎麼做? 今天就要來稍微說明一下,2015 Android Dev Summit提到的做法.

在說明如何測試之前,必須先說明一下gradle flavor,如果不知道gradle flavor的可以參考一下之前的文章
Gradle Flavor ,flavor 的好處是,假設今天我們有兩種flavor,
normalMode & mockMode,當我需要建置正常的版本時,我只需要使用normalMode flavor就好,當我需要建置debug mode時,我只需要使用mockMode就好了.

不過這樣有什麼好處?

讓我們繼續看下去….

同樣一個getUsers()的method
在normalMode底下回傳的資料是真正api 打到server的資料
而在mockMode時則是回傳我們自己偽造的資料

以這種idea去設計的話,我們可以想像出一種架構

interface IApiService{
    Users getUser();
}

然後在normalMode時

NormalService implements IApiService{
    public User getUser(){
        //do api call and get User Model
        //....

        return user;
    }
}

然後在mockMode時則是

MockService implements IApiService{
    public User getUser(){
        //mock test data 
        User user = new User();
        user.name = "ted";
        return user;
    }
}

所以依照上面的範例,我們只需要分別在normal and mock 的folder裡將這兩個檔案放入就完成了,之後只要利用flavor的切換就可以切換成正式資料或測試資料了.

那…..講了這些又跟retrofit有什麼關係?

利用retrofit 我們可以很輕易地做到剛剛這些事情

舉個例子

SimpleService.GitHub github = Injection.getInjection();

    // Create a call instance for looking up Retrofit contributors.
    Observable<List<SimpleService.Contributor>> call = github.contributors("square", "retrofit");

    // Fetch and print a list of the contributors to the library.
    Log.d("Ted","fire");
    call.observeOn(Schedulers.io())
            .subscribeOn(Schedulers.newThread())
            .subscribe(new Action1<List<SimpleService.Contributor>>() {
                @Override
                public void call(List<SimpleService.Contributor> contributors) {
                    for (SimpleService.Contributor contributor : contributors) {
                        Log.d("Ted",contributor.login + " (" + contributor.contributions + ")");
                    }
                }
            });

稍微講解一下上面的code

  1. 首先我們先取得可以call github api 的service
  2. 接著call api github.contributors();
  3. 接著等api 回應,並且顯示log

為了達到 更換資料的
所以我們必須想辦法讓 Injection.getInjection()丟出不同的service

是不是跟第一個sample的概念很像?

所以我們只需要在normalMode讓api真的去打server

public class Injection {

public static SimpleService.GitHub getInjection(){
    Retrofit retrofit = new Retrofit.Builder()
            .baseUrl(SimpleService.API_URL)
            .addConverterFactory(GsonConverterFactory.create())
            .addCallAdapterFactory(RxJavaCallAdapterFactory.create())
            .build();
    return retrofit.create(SimpleService.GitHub.class);
}
}

然後在 mockMode 的時候製造假的資料

    public static SimpleService.GitHub getInjection() {

    // Create the Behavior object which manages the fake behavior and the background executor.
    NetworkBehavior behavior = NetworkBehavior.create();
    // Create the mock implementation and use MockRetrofit to apply the behavior to it.
    NetworkBehavior.Adapter<?> adapter = RxJavaBehaviorAdapter.create();
    MockRetrofit mockRetrofit = new MockRetrofit(behavior, adapter);
    MockGitHub mockGitHub = new MockGitHub();
    SimpleService.GitHub gitHub = mockRetrofit.create(SimpleService.GitHub.class, mockGitHub);
    return gitHub;
}

這樣就行了,之後只需要在開發的時候切換到mockMode就可以使用假的資料去做開發了,是不是很方便啊!!

順便提一下retrofit 有提供mock的lib 讓我們可以方便的mock假的rx資料以及call<>資料
compile ‘com.squareup.retrofit:retrofit-mock:2.0.0-beta2’
compile ‘com.squareup.retrofit:adapter-rxjava-mock:2.0.0-beta2’
而且還可以利用NetworkBehavior的setDelay來延遲api回傳的時間,真的非常的方便.

最後

還是要附一下開發的連結retrofit_with_gradle_flavor

大家快點去嘗試看看吧!!