LoginSignup
3
2

More than 5 years have passed since last update.

KotterKnife拡張して初期化関数を引数に持てるようにしたよ!

Last updated at Posted at 2017-06-23

はじめに

KotterKnifeを使うと、View初期化でおなじみのfindViewById()の処理を以下のようにスッキリかけるのはご周知のとおりだ。

SomeFragment
private val swipeLayout:SwipeRefreshLayout by bindView<SwipeRefreshLayout>(R.id.swipe_layout)

2017/6/26
「課題というか注意事項」を追記

Github

Githubは以下のとおり
https://github.com/kiuchikeisuke/kotterknife

Mavenには上がってないので、srcの中のButterKnife.ktをコピペして使用(PR送ろうかどうかは・・・迷い中)

KotterKnifeを使っても残る問題

だが、我々がViewを使う場合、リソースの割り当てだけでなく、いくつかのパラメータも共通で初期化したいというニーズがある(ある..よね?)。加えて、レイアウトXML側では初期化しずらいパラメータも存在する。上記のSwipeRefreshLayoutでいえば、スワイプ時の更新中アイコンの色などがそうだ。
このようなパラメータはKotterKnifeを使用しても依然としてonCreateやonViewCreatedで初期化処理を書かなければならなかった。(間違ってたらゴメンナサイ..)

SomeFragment
    override fun onViewCreated(view: View?, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)    
        swipeLayout.setColorSchemeResources(R.color.swipe_icon_color, R.color.swipe_icon_color, R.color.swipe_icon_color, R.color.swipe_icon_color)
    }

KotterKnifeの拡張

そこで、KotterKnifeを拡張し、初期化関数を引数に取れるようにすることで、変数の定義と同時に初期化処理を記載して、onCreateonViewCreatedでの記載を丸々消せるようにした。

具体的には以下のようになる

SomeFragment
    private val swipeLayout:SwipeRefreshLayout by bindView<SwipeRefreshLayout>(R.id.swipe_layout, {
        swipeRefreshLayout ->
        swipeRefreshLayout.setColorSchemeResources(R.color.swipe_icon_color, R.color.swipe_icon_color, R.color.swipe_icon_color, R.color.swipe_icon_color)
        swipeRefreshLayout
    })

この拡張されたbindView関数の実装は以下のようになっている

KotterKnife
/* 拡張したメソッドの一部 */
public fun <V : View> View.bindView(id: Int, exInit: (V) -> V)
        : ReadOnlyProperty<View, V> = required(id, viewFinder, exInit)
@Suppress("UNCHECKED_CAST")
private fun <T, V : View> required(id: Int, finder: T.(Int) -> View?, exInit:(V) -> V)
        = Lazy({t: T, desc -> exInit(t.finder(id) as V? ?: viewNotFound(id, desc)) })

bindViewメソッドの第2引数として、「Vを受け取りVを返す関数(exInit)」を引数に取る。そしてexInit関数はrequired内のLazy関数の中で呼び出され、exInitの処理を通したものが最終的にbindViewメソッドの戻りとして返される
これによって、SomeFragment側では、第2引数に実装した関数が初期化時に実行され、setColorSchemeResourcesが実行される。

多分便利な使い方

上記の例では匿名関数で記載したが、もちろん普通の関数としても記載できる

SomeFragment
    private val swipeLayout:SwipeRefreshLayout by bindView<SwipeRefreshLayout>(R.id.swipe_layout, {initSwipeLayout(it) })
    private fun initSwipeLayout(v:SwipeRefreshLayout):SwipeRefreshLayout {
        v.setColorSchemeResources(R.color.swipe_icon_color, R.color.swipe_icon_color, R.color.swipe_icon_color, R.color.swipe_icon_color)
        return v
    }

言い換えれば、処理の共通化が簡単に行えることになる。
今回の例で言えば、引き下げ更新時のくるくるアイコンの色などはアプリ全体を通して同じ色にするだろう。しかしActivityがFragmentが異なれば、毎回setColorSchemeResourcesメソッドを呼び出し、色を設定しなければならない。そのため、バグの混入の原因にもなりうる。
そこで、このような初期化処理をCommonクラスなどにinitSwipeLayoutColorとして実装しておき、各画面ではinitSwipeLayoutColorメソッドを引数に取るだけでシンプルかつ効率的なな初期化を実現できるようになる。

課題というか注意事項

by bindView(~)では遅延評価が行われている。つまり、変数初期化がされるのは、初めてその変数が呼ばれたタイミングである
そのため、変数が一度も呼ばれないとそもそも初期化が走らない
これが影響してくるのはonClickなどの画面操作系のイベント。
今回の引数でonClickなどを実装した結果、変数が一度も呼ばれないような実装になった場合(変数がnever usedの警告を受けている状態)、変数初期化のタイミングがなくなり、onClickのイベントが走らなくなります。

対策としては単純に変数を呼び出せばOK...なのですがそれだと無駄なコードを書くことになってしまい本末転倒。。
なので、一番無難な方法はイベント系の初期化は今回の拡張変数を使わず、今まで通りonCreate/onViewCreatedなどで実装してあげることかな〜と思います。

もし良い解決案のアイデアがあればお願いします。。

3
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
2