2016年9月10日 星期六

Android 開發 (120) DiffUtil

前陣子android 發佈了新版的support lib 24.2.0,稍微看了一下,發現有個非常實用的新class

DiffUtil  先說說它的功用吧,還記得

mAdapter.notifyItemChanged();
mAdapter.notifyItemInserted();
mAdapter.notifyItemRemoved();



這些可以上我們展現出加入移除或ui更動時的動畫,但是...
利用這種做法,我常常會不小心遺漏掉某些item 忘了notifychange 造成crash,
現在google 出了一個新的lib DiffUtil 只要記住三個步驟即可

1. DiffUtil.calculateDiff
2. 更新adapter 的list
3. call dispatchUpdatesTo

你只需要定義新的list 並且實作某幾個function就可以達到這個功能



首先使用DiffUtil.calculateDiff (UserCallBack會定義哪些item是相同或不同)

接著只需要將新的list 塞到adapter裡面並且call dispatchUpdateTo就可以了


UserCallBack
areItemsTheSame,代表著是不是同一個東西,例如商品的id
areContentsTheSame,代表著內容是不是相同,例如這個商品是否有被選取,或者商品的數量是否有不同

接著只要套上剛剛的邏輯,就可以輕易的完成動畫效果
但是請大家注意一下官方文件  https://developer.android.com/reference/android/support/v7/util/DiffUtil.html

文件中有提到,如果在UI Thread 做大量的更新可能會造成等待過久,造成crash
建議在大量更新時使用background thread 做更新

不過小量的更新還是可以直接使用
最後還是要附上sample code


2016年8月18日 星期四

Android 開發(119) cucumber


cucumber 什麼是cucumber ?  cucumber 是一種BDD的工具

使用它有什麼好處?   我們可以看到邏輯



看了上面的code我相信沒有人看得懂在寫什麼

但是看了下面的code我相信每個人都知道我想表達什麼


使用cucumber我們可以將上面的code 轉化成下面單純的幾個步驟,
當然裡面實作的code還是避不掉,但是我們可以將測項,轉化成幾個簡單的字
減低維護的成本,增加可讀性

那使用cucumber有什麼好處了



首先它提供了很醜的介面XD , 不過勉強可以看,

還有一個好處是他可以將截圖直接附在report中



不過目前有個很大的缺點就是,報告只會產生在device的sdcard 中,所以必須想辦法將它取出來


為了能夠順利的將資料取出,所以我設定了特定的位置,將報表放在sdcard的位置裡

接著寫了一個簡單gradle task,在test case 結束的時候將檔案拉到指定位置


利用這種方式,我們可以產生出比原本android studio內建更完整的報表(雖然畫面有點醜...)
有興趣的大家可以試試看,我覺得是蠻實用的,不過要花點時間研究就是了

有時間再將sample code分享給各位看看囉

[經驗分享] 如何避免搶資源?

先講實例:今天有多個任務需要執行,我們希望做個service能夠取得必須執行的任務



以上是架構圖,有多個client,當client有空的時候,就發送request 跟server 要資料,
這個架構看起來很美好,但是實際執行會發現,client常常會拿到同一個任務

why?

因為當多個client 同時執行時,DB的資料會在還沒被改動前就會被讀取數次,
那我們要怎麼避免這件事情發生?

ans: 讓DB只能被一個client存取

所以我修改了設計

可以注意到,原本單純server的地方被改成queue,也就是說所有的request 都會直接進queue,然後queue一次只允許一個client去取得任務,這樣的架構就可以避免同樣的任務被執行多次了

不過目前的架構也會造成許多client在排隊等queue執行完,不過目前這樣的架構已經可以符合我的需求,或許在未來會有其他更優化的架構吧~

2016年3月23日 星期三

Android 開發(118) FAB material design with viewpager

根據material design 的 best practice 要求如果在每個頁面的icon不同,
必須出現animation的動作如下

這是很合理的行為
但是.... google 的sample 卻沒有教我們怎麼實做這個方法

今天花了一點時間研究了一下該如何實作出這樣的效果(以現有api,而不去使用自幹animation的方法)



其實就是在viewpager 的 onPageSelected的時候 直接call
fab.hide() 然後在fab 消失的時(onHidden被call的時候)再將新的圖換上,並且call fab.show()
目前看起來一切都很正常,但是比較好的做法還是應該在onHidden做點判斷
避免animation回來的時候,view消失就crash了

最後,如果大家有其它的做法也歡迎分享給我知道XD

2016年3月12日 星期六

Android 開發(117) what's new in N - java8 - Default and static interface methods

Android N 支援了java8部份的語法,今天要介紹的是
Default and static interface methods,

熟悉interface的大家應該都知道interface 只能定義"行為"不像是abstract class
除了定義行為之外還可以實作相關的內容,舉個最簡單的例子

這是我們熟悉的interface 只能定義"行為"


這是我們熟悉的abstract class 可以定義相關實作內容


不過這件事情在java8出現了改變
在java8 你可以針對interface 定義預設的行為,只要你不實作該功能,
他就會使用default的方法

如下,我針對DefaultInterface 定義了一個預設的getViewType行為


然後我並沒有在DefaultImp 裡面override getViewType這個method


所以當我new DefaultImp().getViewType()時會出現 DefaultInterface的 message

相對的假設我今天override了getViewType則message 會出現我override的方法
如下,假設我使用new CustomImp().getViewType則會出現 CustomImp的message


不過,這樣interface 和abstract的定義看起來就有點模糊了?

其實不會..

interface 還是只能定義"行為" CustomImp 並不能使用super.getViewType這種call 法
也不能定義field ,只能說interface的功能變強大了點!? 多了支援default 行為的這種功能

順道提一下現在interface 也可以定義static method了,不過這個看起來就跟原本的static method沒什麼太大差別,所以在這邊就不提了

android 上可以使用的java8 功能還有很多,會在之後再跟大家提到

2016年3月11日 星期五

Android 開發(116) what's new in N - resizeable activity

Android N 在最近這幾天出了preview版,身為一個android 開發人員當然要嘗鮮一下

今天要介紹的是 resizeableActivity

大家先看一下圖



簡單的說就是可以在一個頁面下開兩個activity ,你只需要長按就可以將有支援的activity
放在另一個視窗

今天就要來說明我們該如何實作

首先你必須將 gradle 升級到N 外加N的模擬器or 手機
接著你必須將gradle的設定先升到N preview的版本



注意 compilesdk, buildTool, minsdk, targetsdk, dependencies的設定

接著Android Manifest必須做一個設定



可以注意到有新增一個taskAffinity ,目前還不清楚為何,但是如果沒有設定就不會work

最後是呼叫activity的方法



FLAG_ACTIVITY_LAUNCH_ADJACENT是指當spilte mode開啟時,我們可以直接將activity開在新的視窗,而不會直接蓋在原本的視窗

FLAG_ACTIVITY_NEW_TASK 根據官方文件的說法是,避免activity直接在同一個task開啟(造成在同個視窗開啟)

也就是說利用上面兩個flag就可以正常的在新的視窗中開啟我們的activity

接著如果想要知道acitivty的size ,我們可以在configChange裡面得到相關的資訊
前提是必須先在manifest裡面加上相關設定,如下





根據官方說法,之後還會有free-form multi-window mode
不過目前還沒看到相關功能就是了,大家可以期待一下囉!!

2016年3月3日 星期四

Android 開發(115) new Design lib bottomSheet

最新發佈的support library 23.2.0 多了一個新的ui bottomSheet

算是一種蓋半頁的ui,常常可以在電商的app裡看到類似的用法






















以前要做這種ui真的是要自己想辦法處理,現在google提供了新的方案
讓我們可以輕鬆做到這個功能

接下來讓我們看看這個lib 實際運作的樣子



要怎麼做到? 方法其實很簡單
只要在想要使用bottomsheet的 layout 上面加上
app:layout_behavior="android.support.design.widget.BottomSheetBehavior"即可
就會出現像上面的運作樣式

如下圖



那如果你希望可以有背景暗掉的感覺bottomsheet也提供使用dialog的做法
如下圖



你需要將想要放入bottomSheetDialog的 ui 利用dialog.setContentView塞進去之後
接著call dialog.show即可



如果在ui 出現或藏起來的情況下,想要自動讓ui 彈出來或縮起來的話
則必須使用BottomSheetBehavior 的setState method ,
裡面有個STATE_EXPANDED的參數



不過這個方法很hardcode,你可以看到先將LayoutParams強轉成CoordinateLayoutParams然後再強轉成bottomSheetBehavior

還好google 有提供比較簡單的做法



只需要使用BottomSheetBehavior.from就可以達到我們的目標了


最後要再提一個peekheight
我們常常可以看到有部分的ui 會凸出一小部分,讓你知道是可以滑動的
google 也提供了類似的功能
我們只需要設定peekheight,當bottomsheet在縮下去的時候就不會縮到完全不見
而是根據你給定的高度顯示



好了,說了那麼多,相信大家都對bottomsheet有基本的了解了
最近google真的出了很多新的ui ,真的讓開發app簡單很多啊!!!

2016年2月26日 星期五

Android 開發 (114) retrofit with rxjava = callback hell?

最近有機會研究了一下retrofit + rxjava ,使用起來真的很方便
不過實際套用到project裡之後,發現了一些可怕的東西


首先我們先看一下下面這段code


我在某個頁面必須發送兩個api,而第二隻api 必須根據前一隻api的結果從相對應的動作,
這樣看起來似乎還好,還算看得懂,雖然中間的if else 有點看不出來是哪隻api的

但是當有三隻四隻五隻互相相依的api那會變怎樣?
















上面的程式碼還沒有加上if else 還有一些邏輯....
我相信大部分的人看到這樣的程式碼加上一堆if else 的話都會直接回應WTF!!!
為了避免之後幫我維護程式碼的其他同事著想
我稍微上網查了一下是否有辦法避掉這個可怕的結構

解法
我們可以使用flapMap operation 來避掉這個問題

如下面的範例



首先,所有api 的邏輯都可以分散在各個 flapmap 而且可以很清楚的看懂,
而且,不會有callback hell的問題



再回來看一下剛剛四隻api的寫法
是不是好懂多了!!

不過今天講的解法只能使用在serial call,至於parallel call 要怎麼處理?

那就是下次的事情了XD

2016年2月23日 星期二

Android 開發 (113) Best Practices for Unique Identifiers

最近在逛android developer的時候,無意間發現了這個頁面
http://developer.android.com/intl/zh-tw/training/articles/user-data-ids.html
裡面說明了各種用來辨識user的情境,以及可以用什麼資料來判斷才是正確的解法,


舉例來說,以前我們總是利用
//Returns the unique device ID, for example, the IMEI for GSM and the MEID or ESN for CDMA phones.

TelephonyManager tm = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
String id = tm.getDeviceId();


或者是Android ID
id = Secure.getString(context.getContentResolver(), Secure.ANDROID_ID);


如今google 推薦可以使用mac address,但是必須使用官方的取得方式才行
如下


裡面還有提了如何預防DDOS的方法


不過似乎是android 限定
http://developer.android.com/intl/zh-tw/training/safetynet/index.html



利用api 取得JwsResult之後,假設 ctsProfileMatch = true
代表驗證成功,接著還必須利用api 再次驗證資料無誤,才算認證成功



上面的連結其實還提供了很多相關的情境,歡迎大家在實作的時候可以拿去參考

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是否都跟預期的一樣美好?