
.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' }
<android.support.design.widget.NavigationView android:id="@+id/navigation" android:layout_width="wrap_content" android:layout_height="match_parent" android:layout_gravity="start" app:headerLayout="@layout/headerlayout" app:menu="@menu/menu_main" />
classpath 'com.android.databinding:dataBinder:1.0-rc0'
apply plugin: 'com.android.databinding'
<?xml version="1.0" encoding="utf-8"?><layout xmlns:android="http://schemas.android.com/apk/res/android"> <data> <variable name="user" type="com.designsupportlibrary.normaldatabinding.NormalUser"/> </data> <LinearLayout android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent"> <EditText android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@{user.firstName}"/> <EditText android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@{user.lastName}"/> <Button android:id="@+id/btn" android:layout_width="wrap_content" android:layout_height="wrap_content" /> </LinearLayout></layout>
public class NormalUser { private String firstName; private String lastName; public String getFirstName() { return firstName; } public String getLastName() { return lastName; } public NormalUser(String firstName, String lastName) { this.firstName = firstName; this.lastName = lastName; } public void setFirstName(String firstName) { this.firstName = firstName; } public void setLastName(String lastName) { this.lastName = lastName; } }
tabLayout.setTabTextColors(Color.BLUE,Color.parseColor("#FF2750"));想要更換tab的顏色時,卻無法正確更換,
xmlns:android.support.design="http://schemas.android.com/apk/res-auto"才能正確使用,fabSize有分normal and mini如上圖,就是正常size和mini size
compile 'com.android.support:design:22.2.0'
<android.support.design.widget.TextInputLayout android:id="@+id/inputlayout" android:hint="name" android:layout_width="wrap_content" android:layout_height="wrap_content" > <EditText android:hint="name" android:layout_width="wrap_content" android:layout_height="wrap_content" /></android.support.design.widget.TextInputLayout>
TextInputLayout inputLayout = (TextInputLayout)findViewById(R.id.inputlayout);inputLayout.setHint("name");
ComponentName serviceName = new ComponentName(this, MyJobService.class);JobInfo jobInfo = new JobInfo.Builder(JOB_ID, serviceName) .setPeriodic(10000) .setRequiresCharging(true) .build();JobScheduler scheduler = (JobScheduler) getSystemService(Context.JOB_SCHEDULER_SERVICE);int result = scheduler.schedule(jobInfo);
public class MyJobService extends JobService { @Override public boolean onStartJob(JobParameters jobParameters) { //do something Log.d("Ted","startJob"); return true; } }
Notification notification = new Notification.Builder(MainActivity.this).setSmallIcon(R.drawable.ic_launcher).setFullScreenIntent(contentIntent, true).addAction(R.drawable.ic_launcher,"add",contentIntent).build();
<head>
   
    <meta property="al:android:url" content="com.mypackage://story/1234">
    <meta property="al:android:package" content="com.mypackage">
    <meta property="al:android:app_name" content="myappName">
    <meta property="og:title" content="title" />
    <meta property="og:type" content="website" />
    
</head>
其中url 為 app要處理的資料,利用該資料就可以知道該user是在哪個頁面,我們就可以將app導向特定的頁面<activity android:name=".MainActivity"
    android:label="@string/app_name" >
    ...
    <intent-filter>
        <action android:name="android.intent.action.VIEW" />
        <category android:name="android.intent.category.DEFAULT" />
        <data android:scheme="com.mypackage" />
    </intent-filter>
</activity>
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    FacebookSdk.sdkInitialize(this); 
    ...
    Uri targetUrl =
      AppLinks.getTargetUrlFromInboundIntent(this, getIntent());
    if (targetUrl != null) {
        Log.i("Activity", "App Link Target URL: " + targetUrl.toString());
    }
}
targetUrl就是特定的網址,我們就可以利用該url判斷應該導到app的哪個頁面curl https://graph.facebook.com/app/app_link_hosts \
-F access_token="APP_ACCESS_TOKEN" \
-F name="Android App Link Object Example" \
-F android=' [
    {
      "url" : "sharesample://story/1234",
      "package" : "com.facebook.samples.sharesample",
      "app_name" : "ShareSample",
    },
  ]' \
-F web=' {
    "should_fallback" : false,
  }'
接著你會得到一組id{"id":"643402985734299"}
接著在利用下方網址 將上面的id 帶入curl -G https://graph.facebook.com/643402985734299 \-d access_token="APP_ACCESS_TOKEN" \
-d fields=canonical_url \
-d pretty=true
接著會得到一組網址{
   "canonical_url": "https://fb.me/643402985734299",
   "id": "643402985734299"
}
這個網址就是我們創造出來的網址,我們可以將這個網址貼到facebook上,只要有人點擊該link 有裝app 就會前往我們的app,沒有app的就會被導向googleplay