この記事はKotlin Advent Calendar 2015の最終日の投稿です。
前日はyy_yankさんのKotlinのSeleniumライブラリKebabを作ってるでした。
初日はngsw_taroさんの俺とKotlinの馴れ初めと歩み 〜正式リリースに向けて〜でした。
はじめに
現在、私は社内で新規の動画系サービスのAndroid版の開発を担当しています。2015年の4月から開発しているのですが、その約1ヶ月前からKotlin導入のための検証を始め、開発開始と同時に導入を決めて開発を進めて今に至ります。この記事ではそのプロジェクトの8ヶ月間のKotlinでの開発をまとめました。この記事がこれからKotlinでAndroidの開発をする方々に少しでも役立てばと思います。
また同カレンダーにはプロジェクトメンバーによる記事もあります。
現在のプロジェクトについて
Androidのエンジニアは自身も含めて4名です。
Kotlinのversionは1.0.0-beta-4583。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速度をあげて開発しています。
kapt
Dagger2のアノテーションはkaptを利用すればKotlinのコードからでも利用可能です。しかし、現在のプロジェクトはCircleCIでビルドをしており、一度Dagger2用の全てのコードをKotlinに移行した際CircleCI上でのみビルドがこけるという現象に直面したため、現在もaptを利用しDagger2用のコードはJavaで記述するという実装になっています。ただ、このkaptへの移行もKotlinのバージョンがM13の時だったので、時間ができればまた再チャレンジをしたいと考えています。
Libraries
build.grade
のdependenciesは以下のようになっています。
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を利用していないため、以下のライブラリは導入を断念しました。
またKotlin独自のライブラリとして以下の2つを採用しています。
KotterKnife
ButterKnifeのkotlin版です。以下のような感じでViewのfindViewById
を行うことができます。
private val viewPager: ViewPager by bindView(R.id.viewPager)
KotterKnifeは内部でProperty Delegationが使われています。Property Delegationについては2日目の記事、みんな大好きKotlinのDelegationについて #ktac2015で詳しく書かれています。
RxBinding-kotlin
RxBindingをKotlin用に拡張したライブラリで、Kotlinから使いやすようにExtension Functionsが定義されています。
viewPager.pageSelections()
.filter { it > 0 && it >= adapter.count - 1 }
.subscribe { loadNext() }
上記の例はViewPagerが最後のPageまでフリックされたら続きを読み込む例です。RxJavaとKotlinのExtension Functionsを使うことで完結に記述することができます。(実際はload中はブロックする、RxLifecycleを用いてunsubscribeするなどの処理が必要です。)
RxBinding-kotlinについては2016年1月15日のAndroidでKotlin勉強会 @ Sansanにて発表する予定です。
追記 : 発表しました。資料は以下にあります。
https://speakerdeck.com/satorufujiwara/rxbinding-kotlin-number-kotlin-sansan
Sample
Dagger2など上記のライブラリを導入したプロジェクトのサンプルはGitHub上に公開しています。
Kotlin Android Snippets
KotlinでAndroidを開発していてKotlinらしくAndroidを便利に開発できたと感じたコードをいくつか紹介します。
Nullable
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
private val pagerAdapter: MainPagerAdapter by lazy { MainPagerAdapter(activity) }
上記はContext(Activity)
が必要なPagerAdapter
でFragment
内に記述されています。Property Delegateは初回呼び出し時に初期化されるため、この例の場合はFragment.onViewCreated
内の以下のタイミングで初期化されています。
viewPager.adapter = pagerAdapter
pagerAdapter
をval
かつNon-Nullに保つことができます。
apply
applyはkotlinのscope functionsの一つです。scope functionsについては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) }
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
などに用いています。
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に処理を記述することができます。
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ごとに必要な処理がブロック化され、可読性が向上します。
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とで作る場合の比較
Pros
Kotlinを採用して最高でした。もうJavaには戻れないというのが正直なところです。
利点は既に例を挙げたとおりですが、それ以外も簡単に列挙しておきます。
- Nullable / Non-Null
- NullPointerExceptionを長らく見てません。
- when
- 特に文字列比較。
- ラムダ式 & 拡張関数
- RxJava/RxBindingとの相性が抜群です。拡張関数と同様のことをJavaでやろうとするとUtil地獄になると思います。
- Data Class
-
toString()
が実装されているのが地味に嬉しい。 - Named Arguments
- Data Classのコンストラクタでは必ず使っています。
最後に
8ヶ月間のKotlinでの開発でチームメンバーとKotlinについて議論をしていく中で、最初に比べるとずいぶんとKotlinらしくコードも洗練されてきたように思います。また、Kotlinの1.0.0ももうすぐ出ると思うので、これからもいろいろと試行錯誤しながらKotlinで開発していくつもりです。
今もしくはこれからKotlinでAndroid開発をする方がいたら、KotlinSlack日本語や勉強会( AndroidでKotlin勉強会、shibuya.apk )などでぜひ情報交換をしましょう!
今年も一年お疲れさまでした。
Links
最後にリンク集を貼っておきます。
- Kotlin Blog
- 最新情報はだいたいここ。コメント欄も必読です
- Kotlin Reference
- Using Project Kotlin for Android by Jake Wharton
- ADVANCING ANDROID DEVELOPMENT WITH THE KOTLIN LANGUAGE by Jake Wharton
- Otaku, Cedric's blog
- Kotlin ❤ FP
- Kotlinらしいコード #jkug by Taro Nagasawa
- Android開発を受注したからKotlinをガッツリ使ってみたら最高だった
- kotlinlang.slack.com
- kotlinlang slack 日本語