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

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

2015年11月28日 星期六

Android 開發(一百零五) 架構建立,利用retrofit & rxjava 來將資料與UI分離

由於最近有點空閒,所以花了點時間看了一下retrofit & rxjava 搭配起來可以做到什麼成果

在講解之前,不知道大家有沒有一樣的經驗,在寫project時,api的code總是跟UI code綁在一起,造成程式邏輯上很複雜,舉個例子來說,例如 https://api.github.com/repos/square/retrofit/contributors 這隻api ,我希望針對回來的json,filter 出login field為JakeWharton的人,以及contributions field大於10的人,並且分別在UI上做相對應的顯示.

可想而知,作法就會是

  1. 取得json List
  2. 針對List filter 出login field 為JakeWharton的物件
  3. 針對List filter 出contributions 大於 10的物件
  4. 顯示到UI上

從code上面看起來就會

List<GithubJson> list = api.getGithubJson();
List<GithubJson> JakeWhartonList = dofilterEquals(list,   "login", "JakeWharton");
loginListCustomView.setData(JakeWhartonList);
List<GithubJson> OverTenList = dofilterOver(list, "contributions", 10);
contributionsListCustomView.setData(JakeWhartonList);

這樣看起來似乎很漂亮…..那是因為這只是個範例,實際上的code應該會比上面這段code複雜好幾倍,所以這也就讓我們開始思考是否有更漂亮的解決方案.

上面這段code最主要的問題在於,資料,處理資料以及UI顯示的程式碼被混雜在一起,造成之後維護上的麻煩,而且重點是無法測試,如果將上面的code寫在activity or fragment 裡,就會發現當ui 呈現錯誤時,無法知道到底是ui錯誤,資料錯誤或是filter錯誤.

所以為了達到我們想要的目標可測試的程式首先就必須先把ui & 資料分離,目標

MyAppClient apiclient = new MyAppClient();
    apiclient.getFilterAndOverTen(new MyAppClient.ICallBack() {
        @Override
        public void callBack(MyWrapper o) {
            //use MyWrapper to updateUI;
        }
    });

為了達到這個目標,我們可以利用rxjava的特性

SimpleService.GitHub github = retrofit.create(SimpleService.GitHub.class);

Observable<List<SimpleService.Contributor>> call = github.contributors("square", "retrofit");

取得observable之後也就是我們想要取得的資料,接著針對資料做filter的動作

       Observable<List<SimpleService.Contributor>> a = call.observeOn(Schedulers.io())
            .subscribeOn(Schedulers.newThread())
            .flatMap(new Func1<List<SimpleService.Contributor>, Observable<SimpleService.Contributor>>() {
                @Override
                public Observable<SimpleService.Contributor> call(List<SimpleService.Contributor> contributors) {
                    return Observable.from(contributors);
                }
            })
            .filter(new Func1<SimpleService.Contributor, Boolean>() {
                @Override
                public Boolean call(SimpleService.Contributor contributor) {
                    return contributor.login.contains("JakeWharton");
                }
            }).toList()
            .doOnNext(new Action1<List<SimpleService.Contributor>>() {
                @Override
                public void call(List<SimpleService.Contributor> contributors) {
                    Log.d("Ted", "size " + contributors.size());
                }
            });

首先我們要先filter出login field 為jakewharton 的人,所以我們必須針對list去做filter,花了一點時間做研究後,發現必須將

Observable<List<SimpleService.Contributor>> 轉為 Observable<SimpleService.Contributor>

filter的動作才可以正確執行
上面的程式碼就是做這樣的事情,接著另一部分的filter當然也是依樣畫葫蘆,當兩個東西都filter完成後,接著重點就是將兩個filter好的東西合併在一起

Observable.zip(a, b, new Func2<List<SimpleService.Contributor>, List<SimpleService.Contributor>, MyWrapper>() {
        @Override
        public MyWrapper call(List<SimpleService.Contributor> contributors, List<SimpleService.Contributor> contributors2) {
            return new MyWrapper(contributors, contributors2);
        }
    }).subscribe(new Action1<MyWrapper>() {
        @Override
        public void call(MyWrapper o) {
            Log.d("TEd","go");
            callBack.callBack(o);
        }
    });

可以注意到,zip就是將兩個物件合起來,然後合成MyWrapper,回傳給外層呼叫的fragment or activity,這樣就完成了

之後如果ui有所改變,data還是可以重用,如果data有所改變,修改的程式也只會限定在data那包裡面,封裝的效果就出來了,當然需要測試的話也可以輕鬆地做測試了.

最後,還是要依照慣例附上sample囉 https://github.com/nightbear1009/retrofit_rxjava_architech

2015年9月20日 星期日

Trunk Based Developement 概述

Trunk Based Development

最近看到了google 的一些文章,裡面提到了google & facebook 都使用了Trunk Based Development,覺得很有趣,所有就去查了一些相關的文章
Trunk Based Development最特別的地方就是,新的功能並不是開一個新的feature branch做開發,而是直接在trunk 上做開發,當然依然要遵守,每筆commit都不能太過於龐大的守則
由於Trunk Based Development 會使多個功能同時在同一個branch上開發,這也就衍生了另一個問題,假設某些功能是v1.0要進,某些功能是v1.1才要進,那要怎麼處理?
根據martin fowler的說法,我們必須實作feature toogle,講白了點就是類似開關的功能,根據設定檔開啟或關閉該功能,舉個例子來說,某個版本要進v1.1,但是我們的功能在v0.1 的時候就已經開始開發,所以我們必須實作一個開關,避免v1.0的版本會出現這個不應該出現的功能,接著我們可以在v1.1的時候將開關打開,然後在v1.2的時候將開關這個功能拔除並且移除舊的功能.
又或者,我們可以實作branch by abstraction的方法,其實也是一個開關的概念或者是說switch的概念,當沒有切換的時候就使用舊版的作法,當切換的時候則改成新版的做法,至於為什麼要使用abstraction的關鍵字?
其實這也對應到了 OO的概念
針對介面來撰寫程式,而不要針對實作
所以在實作這個功能的時候,首先必須先將共同的邏輯抽離抽成interface or abstract,然後舊版則實作這個interface or abstract,新版也實作這個interface or abstract,然後最重要的是使用者只針對interface or abstract 做動作,然後我們就可以去做switch的動作

TBD的好處

  • 降低test的成本
    • 如果有多個feature branch也就代表有多個branch要執行test,這相對也提升了test 成本
  • always是可以release 的codebase,並且可以根據需求,修改需要release的功能
    • 可以利用開關開啟或關閉某項功能
    • 可以利用開關使用新版或舊版功能
  • 減少merge conflict的機會
    • 由於所有人都在同一個trunk上開發,只要所有的人都保證commit的code並不是過於龐大,就算遇到conflict也可以快速解決
    • 適合多人同時開發的專案

結論

講了那麼多,其實我也還沒有機會實際run過這個流程,不過根據目前的理解,TBD講白了就是把所有的功能都放在同一個branch上開發,然後使用featuretoggle & branch by abstraction來隔開尚未完成的實作,很合理…但是同時也覺得會有很多坑啊!!

reference

http://www.alwaysagileconsulting.com/organisation-pattern-trunk-based-development/
http://martinfowler.com/bliki/FeatureBranch.html
http://www.martinfowler.com/bliki/FeatureToggle.html
http://paulhammant.com/blog/branch_by_abstraction.html

2015年9月15日 星期二

MarkDown

其實這篇文章是想要稍微推薦一下markdown
markdown其實是個很輕量的語法,由於用google blog的介面寫blog真的很麻煩

  1. 沒有code block
  2. 排版十分不易
  3. 沒辦法即時預覽

這些使用markdown語法 + stackedit 之後所有的問題都迎刃而解啊!!
我想應該會使用這個editor寫一陣子吧!!

順便熟悉一下新editor的用法

Android 開發(一百零四) what's new in andorid studio 1.4 preview

先看一下下面這張圖


這代表什麼?
代表或許在android studio 1.4 正式release 之後,或許就可以支援
使用vector 產生圖檔了!!

有興趣的人可以先去下載新版的android studio 1.4 beta
然後將build.gradle的 gradle版本改成1.4.0-beta1

buildscript {
    repositories {
        jcenter()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:1.4.0-beta1'
        // NOTE: Do not place your application dependencies here; they belong        // in the individual module build.gradle files    }
}

然後就可以使用這項功能了,記得在import圖檔之後要sync一下,接著檔案就會產生了
這對UI真的是一大福音啊!!,之後只要出一張vector就好了!!

不過這項功能目前還沒發佈,可以再期待一下囉!!

Android 開發(一百零三) dex-method-counts

今天要講的是app 的method counts

為什麼我們要注意method counts?
因為android 有65536的method count的限制

如果app的method數超過了65536就會無法build成功,
所以為了提早發現提早治療,所以我們必須常常觀察method數是否超出預期

從前並沒有方便的工具去觀察method數,不過最近觀察到github上有人提供了簡單的檢驗方式 https://github.com/mihaip/dex-method-counts

小弟依照了上面的方式稍微檢驗了我們家的app

其實已經快爆了XD

不過如果你檢驗了app後發現遇到跟我類似的情形的話,其實不太需要擔心
因為google 已經想到了這個,所以他提出了 https://developer.android.com/tools/building/multidex.html

multidex,只要利用multidex就可以解決掉這個問題

根據google 的文件我們首先必須import multidex的lib

dependencies {
  compile 'com.android.support:multidex:1.0.0'
}
接著必須在build.gradle裡加上multidexEnabled

    defaultConfig {
        
        multiDexEnabled true

    }
我們必須將原本的application改成繼承MultiDexApplication

public class MyApp extends MultiDexApplication  {

    @Override
    protected void attachBaseContext(Context base) {
        super.attachBaseContext(base);
        MultiDex.install(this);
    }

這樣就完成了

不過注意一下,multidex還是有一些限制的,
例如必須要在版本14以上,還有可能會造成build的速度變慢之類的問題

最後,如果有寫test case 的人,而且有用AndroidJUnitRunner的話
可能會想問要怎麼在multidex版本上面正常測試

方法很簡單,首先必須在test的folder裡自建一個runner
這個runner必須必得要使用multidex.install

public class MyRunner extends AndroidJUnitRunner {

    @Override
    public void onCreate(Bundle arguments) {
        MultiDex.install(getTargetContext());
        super.onCreate(arguments);
    }
}

接著必須在build.gradle裡加上testInstrumentationRunner

    defaultConfig {
        minSdkVersion 14
        targetSdkVersion 22

        testInstrumentationRunner "com.MyRunner"
        multiDexEnabled true

    }

這樣就完成了,就可以使用舊有的annotation了