
.settext -> .setColor 可以用tab不要用enter
Log.d(TAG, "send() called with: " + "p1 = [" + p1 + "], p2 = [" + p2 + "]");
}
Find Commands : Shift + CMD + A
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
為了達到 更換資料的
所以我們必須想辦法讓 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
大家快點去嘗試看看吧!!
由於最近有點空閒,所以花了點時間看了一下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上做相對應的顯示.
可想而知,作法就會是
從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
由於Trunk Based Development 會使多個功能同時在同一個branch上開發,這也就衍生了另一個問題,假設某些功能是v1.0要進,某些功能是v1.1才要進,那要怎麼處理?
根據martin fowler的說法,我們必須實作feature toogle,講白了點就是類似開關的功能,根據設定檔開啟或關閉該功能,舉個例子來說,某個版本要進v1.1,但是我們的功能在v0.1 的時候就已經開始開發,所以我們必須實作一個開關,避免v1.0的版本會出現這個不應該出現的功能,接著我們可以在v1.1的時候將開關打開,然後在v1.2的時候將開關這個功能拔除並且移除舊的功能.針對介面來撰寫程式,而不要針對實作
所以在實作這個功能的時候,首先必須先將共同的邏輯抽離抽成interface or abstract,然後舊版則實作這個interface or abstract,新版也實作這個interface or abstract,然後最重要的是使用者只針對interface or abstract 做動作,然後我們就可以去做switch的動作其實這篇文章是想要稍微推薦一下markdown
markdown其實是個很輕量的語法,由於用google blog的介面寫blog真的很麻煩
這些使用markdown語法 + stackedit 之後所有的問題都迎刃而解啊!!
我想應該會使用這個editor寫一陣子吧!!
順便熟悉一下新editor的用法
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 } }
接著必須在build.gradle裡加上multidexEnableddependencies { compile 'com.android.support:multidex:1.0.0' }