2016年、KotlinでAndroid開発する方へ

この記事は[Kotlin Advent Calendar 2015](http://www.adventar.org/calendars/857)の最終日の投稿です。
前日はyy_yankさんの[KotlinのSeleniumライブラリKebabを作ってる](http://yyyank.blogspot.jp/2015/12/kotlinseleniumkebab-ktac2015-jkug.html)でした。
初日はngsw_taroさんの[俺とKotlinの馴れ初めと歩み 〜正式リリースに向けて〜](http://taro.hatenablog.jp/entry/2015/12/01/075004)でした。

# はじめに

現在、私は社内で新規の動画系サービスのAndroid版の開発を担当しています。2015年の4月から開発しているのですが、その約1ヶ月前からKotlin導入のための検証を始め、開発開始と同時に導入を決めて開発を進めて今に至ります。この記事ではそのプロジェクトの8ヶ月間のKotlinでの開発をまとめました。この記事がこれからKotlinでAndroidの開発をする方々に少しでも役立てばと思います。

また同カレンダーにはプロジェクトメンバーによる記事もあります。

* [業務でKotlinを書いている僕がKotlinを書く際に個人的に注意していること](http://qiita.com/magie-pooh/items/b1179af28f5e0d50b62a)
* [OperatorとInfixでCustom operatorを作る](http://qiita.com/AAkira@github/items/d2cb56eff8952ecfc66b)

# 現在のプロジェクトについて

Androidのエンジニアは自身も含めて4名です。
Kotlinのversionは[1.0.0-beta-4583](http://qiita.com/mattak@github/items/ba0e3294d539a86f120f)。minSdkVersionは16、compileSdkVersionは23です。メソッド数は約93000メソッド。全体の85%がKotlin、残りの15%はJavaで記述されています。メソッド数が65kを超えているので、multidexを使っています。
Javaで記述されている15%はDagger2の`@Component`,`@Module`,`@Scope`がついているクラス群、Javaでかつて利用していた再利用可能なクラスです。
ビルド速度はフルビルドで約70秒です。
(環境 : MacBook Pro Retina, 15-inch, Late 2013 Processor : 2 GHz Intel Core i7 memory : 16 GB 1600 MHz DDR3)
ただし、開発時は[minSdkVersionを21にしてbuild速度をあげて](http://developer.android.com/intl/ja/tools/building/multidex.html#dev-build)開発しています。

## kapt
Dagger2のアノテーションは[kapt](http://blog.jetbrains.com/kotlin/2015/05/kapt-annotation-processing-for-kotlin/)を利用すればKotlinのコードからでも利用可能です。しかし、現在のプロジェクトはCircleCIでビルドをしており、一度Dagger2用の全てのコードをKotlinに移行した際CircleCI上でのみビルドがこけるという現象に直面したため、現在もaptを利用しDagger2用のコードはJavaで記述するという実装になっています。ただ、このkaptへの移行もKotlinのバージョンがM13の時だったので、時間ができればまた再チャレンジをしたいと考えています。

## Libraries

`build.grade`のdependenciesは以下のようになっています。

```groovy
def appcompatVersion = "23.1.1"
def playServicesVersion = "8.3.0"
def rxBindingVersion = "0.3.0"
dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    // support libs
    compile "com.android.support:support-v4:$appcompatVersion"
    compile "com.android.support:appcompat-v7:$appcompatVersion"
    compile "com.android.support:recyclerview-v7:$appcompatVersion"
    compile "com.android.support:design:$appcompatVersion"
    compile "com.android.support:cardview-v7:$appcompatVersion"

    // multidex
    compile 'com.android.support:multidex:1.0.1'

    // play services
    compile "com.google.android.gms:play-services-base:$playServicesVersion"
    compile "com.google.android.gms:play-services-ads:$playServicesVersion"
    compile "com.google.android.gms:play-services-gcm:$playServicesVersion"
    compile "com.google.android.gms:play-services-analytics:$playServicesVersion"

    // kotlin
    compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion"
    compile 'com.jakewharton:kotterknife:0.1.0-SNAPSHOT'

    // Dagger 2
    compile 'com.google.dagger:dagger:2.0.2'
    apt 'com.google.dagger:dagger-compiler:2.0.2'
    provided 'org.glassfish:javax.annotation:10.0-b28'

    // data
    compile 'com.google.code.gson:gson:2.4'
    compile 'com.squareup.okhttp:okhttp:2.5.0'
    compile 'com.squareup.retrofit:retrofit:1.9.0'

    // cipher
    compile 'com.facebook.conceal:conceal:1.0.1@aar'

    // date
    compile 'com.jakewharton.threetenabp:threetenabp:1.0.2'

    // image
    compile 'com.github.bumptech.glide:glide:3.6.1'
    compile 'com.github.bumptech.glide:okhttp-integration:1.3.1'
    compile 'jp.wasabeef:glide-transformations:1.2.1'

    // reactive
    compile 'io.reactivex:rxjava:1.1.0'
    compile 'io.reactivex:rxandroid:1.1.0'
    compile 'com.trello:rxlifecycle:0.3.0'
    compile 'com.trello:rxlifecycle-components:0.3.0'
    compile "com.jakewharton.rxbinding:rxbinding-kotlin:$rxBindingVersion"
    compile "com.jakewharton.rxbinding:rxbinding-support-v4-kotlin:$rxBindingVersion"
    compile "com.jakewharton.rxbinding:rxbinding-appcompat-v7-kotlin:$rxBindingVersion"
    compile "com.jakewharton.rxbinding:rxbinding-recyclerview-v7-kotlin:$rxBindingVersion"
    compile 'com.f2prateek.rx.preferences:rx-preferences:1.0.0'
    compile 'com.squareup.sqlbrite:sqlbrite:0.4.0'

    // event
    compile 'com.squareup:otto:1.3.8'

    // ui
    compile 'jp.satorufujiwara:recyclerview-binder:1.3.2'
    compile 'jp.satorufujiwara:material-scrolling:1.1.0'
    compile 'com.github.ksoichiro:android-observablescrollview:1.5.2'
    compile 'com.ogaclejapan.smarttablayout:library:1.4.2@aar'
    compile 'uk.co.chrisjenx:calligraphy:2.1.0'
    compile 'com.github.castorflex.verticalviewpager:library:19.0.1'
    compile 'com.github.magiepooh:recycler-itemdecoration:1.1.0@aar'
    compile 'com.github.aakira:expandable-layout:1.4.1@aar'

    // video
    compile 'jp.satorufujiwara:exoplayer-textureview:0.5.1'
    compile 'com.google.android.exoplayer:exoplayer:r1.5.3'

    // socket
    compile 'io.socket:socket.io-client:0.6.1'
}
```

上記のライブラリ以外にログインのためのSDKや、デバッグ用にTimberやStethoなどを導入しています。
Androidで著名なライブラリは問題なしに使えます。
ただkaptを利用していないため、以下のライブラリは導入を断念しました。

* [auto-parcel](https://github.com/frankiesardo/auto-parcel)
* [Parceler](https://github.com/johncarl81/parceler)
* [DeepLinkDispatch](https://github.com/airbnb/DeepLinkDispatch)
* [PermissionDispatcher](https://github.com/hotchemi/PermissionsDispatcher)

またKotlin独自のライブラリとして以下の2つを採用しています。

* [KotterKnife](https://github.com/JakeWharton/kotterknife)
* [RxBinding-kotlin](https://github.com/JakeWharton/RxBinding)

### KotterKnife
ButterKnifeのkotlin版です。以下のような感じでViewの`findViewById`を行うことができます。

```kotlin
private val viewPager: ViewPager by bindView(R.id.viewPager)
```
KotterKnifeは[内部でProperty Delegationが使われて](https://github.com/JakeWharton/kotterknife/blob/master/src/main/kotlin/butterknife/ButterKnife.kt#L97)います。Property Delegationについては2日目の記事、[みんな大好きKotlinのDelegationについて #ktac2015](http://blog.takuji31.jp/entry/ktac2015)で詳しく書かれています。

### RxBinding-kotlin

RxBindingをKotlin用に拡張したライブラリで、Kotlinから使いやすように[Extension Functionsが定義](https://github.com/JakeWharton/RxBinding/blob/0.3.0/rxbinding-support-v4-kotlin/src/main/kotlin/com/jakewharton/rxbinding/support/v4/view/RxViewPager.kt#L14)されています。

```kotlin
viewPager.pageSelections()
         .filter { it > 0 && it >= adapter.count - 1 }
         .subscribe { loadNext() }
```
上記の例はViewPagerが最後のPageまでフリックされたら続きを読み込む例です。RxJavaとKotlinの[Extension Functions](https://kotlinlang.org/docs/reference/extensions.html)を使うことで完結に記述することができます。(実際はload中はブロックする、RxLifecycleを用いてunsubscribeするなどの処理が必要です。)

RxBinding-kotlinについては[2016年1月15日のAndroidでKotlin勉強会 ＠ Sansan](http://connpass.com/event/22189/)にて発表する予定です。

追記 : 発表しました。資料は以下にあります。
https://speakerdeck.com/satorufujiwara/rxbinding-kotlin-number-kotlin-sansan

## Sample

Dagger2など上記のライブラリを導入したプロジェクトのサンプルはGitHub上に公開しています。

https://github.com/satorufujiwara/kotlin-android-example

# Kotlin Android Snippets

KotlinでAndroidを開発していてKotlinらしくAndroidを便利に開発できたと感じたコードをいくつか紹介します。

## Nullable

```kotlin
var str : String? = null
fun main(){
   val str = str ?: return run {
      //if str is null
   }
   //str is not null
   Timber.d("str length = ${str.length}")
}
```
var propertyはSmartCastsが使えませんがlocal variableに代入してあげることでNullable propertyをNon-Null variable に変えることが出来ます。上の例ではnull時に早期リターンしています。

## Property Delegate

```kotlin:MainFragment.kt
private val pagerAdapter: MainPagerAdapter by lazy { MainPagerAdapter(activity) }
```
上記は`Context(Activity)`が必要な`PagerAdapter`で`Fragment`内に記述されています。Property Delegateは初回呼び出し時に初期化されるため、この例の場合は`Fragment.onViewCreated`内の以下のタイミングで初期化されています。

```kotlin:MainFragment.kt_onViewCreated
viewPager.adapter = pagerAdapter
```

`pagerAdapter`を`val`かつNon-Nullに保つことができます。

## apply

applyはkotlinのscope functionsの一つです。scope functionsについては[Kotlin スコープ関数 用途まとめ](http://qiita.com/ngsw_taro/items/d29e3080d9fc8a38691e)が非常に参考になります。

```kotlin
class MainFragment : Fragment() {
 
    companion object {
       @JvmStatic fun newInstance(dto: Dto) = MainFragment().apply {
           arguments = Bundle().apply { putParcelable(EXTRA_DTO, dto) }
       }
       private const val EXTRA_DTO = "extra_dto"
    }
private val dto: Dto by lazy { arguments.getParcelable<Dto>(EXTRA_DTO) }
```
```kotlin
fun createIntent(url: String) = Intent(Intent.ACTION_VIEW, Uri.parse(url)).apply {
    addFlags(
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) Intent.FLAG_ACTIVITY_NEW_DOCUMENT
            else Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET
    )
}
```
Androidではインスタンスを作って値を与えるためにわざわざ変数を用意する場面が多いですが、その時に`apply`を使えば変数を用意する必要がなくなりすっきり書けます。
`Fragment`、`Bundle`、`Intent`、`AppCompatDialog`などに用いています。

```kotlin
fun View.fitLongestWidth(rate: Float) {
    val metrics = DisplayMetrics()
    (context.getSystemService(Context.WINDOW_SERVICE) as WindowManager).defaultDisplay.getMetrics(metrics)
    layoutParams = layoutParams.apply { width = (Math.max(metrics.widthPixels, metrics.heightPixels) * rate).toInt() }
}
```
`LayoutParams`のようにgetして値を変更してから再びsetするものにも`apply`は使えます。

## Property

Kotlinはpropertyの[setter,getterに処理を記述すること](https://kotlinlang.org/docs/reference/properties.html#getters-and-setters)ができます。

```kotlin
private var isAvailableRefresh = false
    set(value) {
        if (value) {
            refreshButton.toVisible()
            if (value != field) refreshButton.scaleIn()
        } else {
            refreshButton.toGone()
        }
        field = value
    }
```
`toVisibile()`などはViewのExtension Functionsです。
上記の例では`isAvailableRefresh`のフラグの変更とともに`refreshButton`のvisibilityを変更しています。その際`value = true && value != field`の時、つまり`false`から`true`値が変更された時にのみ`scaleIn()`でアニメーションをさせています。
setter,getterに処理を書くことによってpropertyごとに必要な処理がブロック化され、可読性が向上します。

```kotlin
class CustomView : FrameLayout {
    var dto: Dto? = null
        set(value) {
            field = value
            value ?: return toInvisible()
            // bind dto to views.
        }
```
CustomViewにオブジェクトを与えて、それによってViewの表示を変える時にも使っています。

# Conclusion

## Cons

Kotlinを採用するにあたり、多くの罠を踏む覚悟をしましたが、大きくつまづいた場所は上記のkaptだけだったように思います。
バージョンアップ時には言語仕様が変更され、コードの変更が必要でしたが、CodeCleanupを利用するなどすればそこまで苦ではありませんでした。現在は1.0.0-betaが出ているので以前ほどの大きな言語仕様の変更はなさそうに思います。
また、multidexを採用できないプロジェクトにとってはメソッド数は問題になるかもしれません。しかし、Kotlinを採用するためのメソッド数は誤差かと考えています。

参考 : [AndroidをKotlinとRetrolamda+Lombokとで作る場合の比較](http://qiita.com/daneko0123/items/3ef2136cdbee913206d1)

## Pros

Kotlinを採用して最高でした。もうJavaには戻れないというのが正直なところです。
利点は既に例を挙げたとおりですが、それ以外も簡単に列挙しておきます。

* Nullable / Non-Null
 * NullPointerExceptionを長らく見てません。 
* [when](https://kotlinlang.org/docs/reference/control-flow.html#when-expression)
 * 特に文字列比較。
* ラムダ式 & 拡張関数
 * RxJava/RxBindingとの相性が抜群です。拡張関数と同様のことをJavaでやろうとするとUtil地獄になると思います。
* [Data Class](https://kotlinlang.org/docs/reference/data-classes.html)
 * `toString()`が実装されているのが地味に嬉しい。 
* [Named Arguments](https://kotlinlang.org/docs/reference/functions.html#named-arguments)
 * Data Classのコンストラクタでは必ず使っています。

# 最後に

8ヶ月間のKotlinでの開発でチームメンバーとKotlinについて議論をしていく中で、最初に比べるとずいぶんとKotlinらしくコードも洗練されてきたように思います。また、Kotlinの1.0.0ももうすぐ出ると思うので、これからもいろいろと試行錯誤しながらKotlinで開発していくつもりです。
今もしくはこれからKotlinでAndroid開発をする方がいたら、[KotlinSlack日本語](http://kotlinlang-jp.herokuapp.com/)や勉強会( [AndroidでKotlin勉強会](http://connpass.com/event/22189/)、[shibuya.apk](http://shibuya-apk.connpass.com/) )などでぜひ情報交換をしましょう！

今年も一年お疲れさまでした。

# Links
最後にリンク集を貼っておきます。

* [Kotlin Blog](http://blog.jetbrains.com/kotlin/)
 * 最新情報はだいたいここ。コメント欄も必読です 
* [Kotlin Reference](https://kotlinlang.org/docs/reference/)
* [Using Project Kotlin for Android by Jake Wharton](https://docs.google.com/document/d/1ReS3ep-hjxWA8kZi0YqDbEhCqTt29hG8P44aA9W0DM8/edit?hl=ja&forcehl=1)
* [ADVANCING ANDROID DEVELOPMENT WITH THE KOTLIN LANGUAGE by Jake Wharton](https://vimeo.com/144877458)
* [Otaku, Cedric's blog](http://beust.com/weblog/category/kotlin/)
* [Kotlin ❤ FP](https://medium.com/@octskyward/kotlin-fp-3bf63a17d64a#.b7hc1pjig)
* [Kotlinらしいコード #jkug by Taro Nagasawa]()
* [Android開発を受注したからKotlinをガッツリ使ってみたら最高だった](http://qiita.com/omochimetaru/items/98e015b0b694dd97f323)
* [kotlinlang.slack.com](https://kotlinlang.slack.com)
* [kotlinlang slack 日本語](http://kotlinlang-jp.herokuapp.com/)