そーす

福岡在住のプログラマ。SwiftとかKotlinとかJavascriptとかSketchとか触ってます。

Kotlin Android Extensionsで注意すること

f:id:saburesan:20160906091652j:plain

とくにハマった訳ではないですがやらかしそうなミスだなぁと思ったので。

Kotlin Android ExtensinsとはViewの参照を自動で作ってくれるAndroidのためのPluginです。

kotlinlang.org

findViewByIdしなくてもID名から自動で参照を作ってくれるので非常に便利です。

f:id:saburesan:20170214104202p:plain

使い方

app以下のbuild.gradleに

apply plugin: 'kotlin-android-extensions'

を追加するだけ。

仕組み

Kotlin側ではR.id.xxxのxxxでViewへアクセスできるので、thisがViewクラスまたはサブクラスの場合、そのViewに対してxxxのIDをfindViewByIdをしているだけです。

Javaデコンパイルするとこんな感じ

ViewPager var7 = (ViewPager)this.findViewById(id.viewPager);

毎回findViewByIdするのはパフォーマンス悪いんじゃないかなーと思ったらキャッシュする機構がちゃんとありました。

...
 private HashMap _$_findViewCache;
...
 public View _$_findCachedViewById(int var1) {
      if(this._$_findViewCache == null) {
         this._$_findViewCache = new HashMap();
      }

      View var2 = (View)this._$_findViewCache.get(Integer.valueOf(var1));
      if(var2 == null) {
         var2 = this.findViewById(var1);
         this._$_findViewCache.put(Integer.valueOf(var1), var2);
      }

      return var2;
   }

   public void _$_clearFindViewByIdCache() {
      if(this._$_findViewCache != null) {
         this._$_findViewCache.clear();
      }

   }
...

この機構ですが、ActivityまたはFragment内でしか使われていないようです。 Viewのサブクラス内だと現状毎回findViewByIdしてるっぽくて、あんまりパフォーマンス良くないようです。 (Fragment無しのViewのサブクラスベースで作ってて後から気づいた…

注意点

インポートに注意

Kotlin Android Extensionsを使うとレイアウトファイルのViewのIDを元に参照できるようにkotlinx.android.synthetic.main.<レイアウト名>.*をインポートしなければいけません。 この時に、複数のレイアウトファイルで同じIDを使っている場合にインポート候補が複数出ます。 もし、間違ったレイアウトをインポートしても静的解析ではエラーは検出出来ません。 仕組みで紹介したようにthisに対してfindViewByIdしているのでViewが無ければヌルポです。

Viewの中でthisが変わった時に注意

Viewのサブクラス内でインナークラスを定義したり、apply, runなどを使ってthisが変わる場合も注意が必要です。 私はapplyで初期値の設定などを行うのですが、

viewPager.apply {
            adapter = pagerAdapter
}
tabLayout.apply {
            addTab(newTab().setText(R.string.item_state_a))
            addTab(newTab().setText(R.string.item_state_b))
            addTab(newTab().setText(R.string.item_state_c))
            viewPager.currentItem = 1
        }

こう書くとviewpagerがヌルポで死にます。 applyに渡されたブロック内ではthisがレシーバになるので例ではtabLayoutからviewPagerを探そうとしてヌルポになります。

まとめ

気をつけていればあまり起こることは無いのかもしれないです。 インポートのミスについては気をつけるしかないのですが、 Viewのサブクラス内ではキャッシュされないであるとかthisが変わる際にミスが起きやすいなどの対策としては インスタンス変数として保持するようにするのが一番効果的だと思います。

private val viewPager by lazy { view_pager } //view_pagerはKotlin Android Extensionsの参照