Slide 1

Slide 1 text

個人開発Androidアプリを Kotlinにガチ移行してみた話 2015/6/5 第3回 かわいいKotlin勉強会 @kirimin

Slide 2

Slide 2 text

自己紹介 @kirimin ● Androidアプリ開発者 (ジャバ) ● エンジニア4年目 フリーランス2年目 ● 現在は圧倒的当事者意識で某社の Androidアプリ開発を手伝っています

Slide 3

Slide 3 text

自己紹介

Slide 4

Slide 4 text

No content

Slide 5

Slide 5 text

今、Androidで Kotlinがアツい!!!!

Slide 6

Slide 6 text

なぜAndroidでKotlin?

Slide 7

Slide 7 text

なぜAndroidでKotlin?(おさらい) ● AndroidではJava8が当分使えなそう ● Kotlinならラムダ式もパイプラインでのリスト操作も書ける ● RxAndroidとも相性が良い ● Android StudioならIDEのサポートが効きすぐに使える ● 学習コストも低く、静的で安全志向な言語仕様は    Androidエンジニアに丁度いい(主観) ● Scalaはいろんな意味で人類にはまだ早い

Slide 8

Slide 8 text

KotlinでAndroid開発がしたい!!!

Slide 9

Slide 9 text

でも、本当に使えるのだろうか?

Slide 10

Slide 10 text

ならば既存アプリを全力で Kotlin化してみよう!!!

Slide 11

Slide 11 text

今回Kotlin化したアプリ https://play.google.com/store/apps/details?id=me.kirimin.mitsumine

Slide 12

Slide 12 text

mitsumine

Slide 13

Slide 13 text

mitsumine ● 約3500行 ● 54クラス ● 使用しているライブラリ ● RxAndroid ● Volley ● ActiveAndroid ● Picasso      など...

Slide 14

Slide 14 text

やったこと ● Kotlin利用準備 ● JavaクラスをひたすらKotlin化 ● Kotlin Android Extensionsを導入 ● もっとKotlinらしくしてみる

Slide 15

Slide 15 text

やったこと ● Kotlin利用準備 ● JavaクラスをひたすらKotlin化 ● Kotlin Android Extensionsを導入 ● もっとKotlinらしくしてみる

Slide 16

Slide 16 text

Kotlin利用準備 ● Android StudioにKotlinプラグインをインストール ● build.gradleにおまじないを記述 だいたいこれだけ。 しかも.ktファイルを作るとポップアップが出てきて自動で やってくれる。

Slide 17

Slide 17 text

やったこと ● Kotlin利用準備 ● JavaクラスをひたすらKotlin化 ● Kotlin Android Extensionsを導入 ● もっとKotlinらしくしてみる

Slide 18

Slide 18 text

JavaクラスをひたすらKotlin化 「Convert Java to Kotlin File」でひたすらポチポチ…

Slide 19

Slide 19 text

JavaクラスをひたすらKotlin化 「Convert Java to Kotlin File」でひたすらポチポチ… ● クラス数が多いと結構だるい… (一斉変換も出来るけど) ● 大抵は自動変換しただけでちゃんと動く ● 時々コンパイルエラーが出る ● 時々実行時エラーが出る ● 時々変な変換されて処理が消える ● 1クラス変換する度にDiffって動作確認した方がハマらない(戒め)

Slide 20

Slide 20 text

JavaクラスをひたすらKotlin化 変換時に遭遇したエラーの思い出 ● Null許容型とNull非許容型で型エラーが出てる →型に?を付けてNull許容型に変えたりして頑張ろう ● overrideメソッドのsuperでエラーが出てる →superのようにどのスーパークラスを呼ぶのか 明示しよう ● CustomViewのコンストラクタでエラーが出てる  →class xxxView: View()の()を消してsecondary constructor の構文を使おう

Slide 21

Slide 21 text

● Kotlin利用準備 ● JavaクラスをひたすらKotlin化 ● Kotlin Android Extensionsを導入 ● もっとKotlinらしくしてみる やったこと

Slide 22

Slide 22 text

Kotlin Android Extensionsを導入 Kotlin Android Extensionsとは ● KotlinのAndroid用公式ライブラリ(プラグイン) ● findViewByIdを葬り去るためのライブラリ ● Activityクラスのimport文でActivityとレイアウトファイルを紐 付けるだけでActivityにViewのプロパティを生やしてくれる ● Butter Knifeの@InjectViewすら必要ない版のようなイメージ ● 手軽に使えてめちゃくちゃ便利

Slide 23

Slide 23 text

Kotlin Android Extensionsを導入  import kotlinx.android.synthetic..* ActivityやFramentにレイアウトファイルを紐付ける。  userNameTextView.setText(“test”); Activityのプロパティになるのでそのまま参照出来る。  getView().userNameTextView.setText(“test”); Fragmentの場合はgetView()で取得出来るルートのViewに生える。

Slide 24

Slide 24 text

● Kotlin利用準備 ● JavaクラスをひたすらKotlin化 ● Kotlin Android Extensionsを導入 ● もっとKotlinらしくしてみる やったこと

Slide 25

Slide 25 text

もっとKotlinらしくしてみる 匿名クラスをラムダ式に書き換える textView.setOnClickListener(object : View.OnClickListener { override fun onClick(v: View) { } }) textView.setOnClickListener { v -> } ↓

Slide 26

Slide 26 text

もっとKotlinらしくしてみる 匿名クラスのままKotlinに変換されたRxJavaの処理も subscriptions.add(EntryInfoApi.request(RequestQueueSingleton.get(getApplicationContext()), url) .subscribeOn(Schedulers.newThread()) .observeOn(AndroidSchedulers.mainThread()) .map(EntryInfoFunc.mapToEntryInfo()) .filter(object : Func1 { override fun call(entryInfo: EntryInfo?): Boolean? { return entryInfo == null } }) .subscribe(object : Action1 { override fun call(entryInfo: EntryInfo) { countLayout.setVisibility(View.VISIBLE) titleTextView.setText(entryInfo.getTitle()) bookmarkCountTextView.setText(String.valueOf(entryInfo.getBookmarkCount())) Picasso.with(getApplicationContext()).load(entryInfo.getThumbnailUrl()).fit().into(thumbnailImageView) val adapter = EntryInfoPagerAdapter(getSupportFragmentManager()) adapter.addPage(BookmarkListFragment.newFragment(entryInfo.getBookmarkList()), getString(R.string.entry_info_all_bookmarks)) subscriptions.add(Observable.from(entryInfo.getBookmarkList()) .filter(EntryInfoFunc.hasComment()) .toList() .subscribe(object : Action1> { override fun call(commentList: List) { commentCountTextView.setText(String.valueOf(commentList.size())) adapter.addPage(BookmarkListFragment.newFragment(commentList), getString(R.string.entry_info_comments)) if (AccountDAO.get() != null) { adapter.addPage(RegisterBookmarkFragment.newFragment(entryInfo.getUrl()), getString(R.string.entry_info_register_bookmark)) } viewPager.setAdapter(adapter) viewPager.setCurrentItem(1) viewPager.setOffscreenPageLimit(2) tabs.setViewPager(viewPager) } })) } }, object : Action1 { override fun call(throwable: Throwable) { Toast.makeText(getApplicationContext(), R.string.network_error, Toast.LENGTH_SHORT).show() }

Slide 27

Slide 27 text

もっとKotlinらしくしてみる 全てラムダ式にしてスッキリ! subscriptions.add(EntryInfoApi.request(RequestQueueSingleton.get(getApplicationContext()), url) .subscribeOn(Schedulers.newThread()) .observeOn(AndroidSchedulers.mainThread()) .map { response -> EntryInfoFunc.toEntryInfo(response) } .filter { entryInfo -> !entryInfo.isNullObject() } .subscribe ({ entryInfo -> countLayout.setVisibility(View.VISIBLE) titleTextView.setText(entryInfo.title) bookmarkCountTextView.setText(entryInfo.bookmarkCount.toString()) Picasso.with(getApplicationContext()).load(entryInfo.thumbnailUrl).fit().into(thumbnailImageView) val adapter = EntryInfoPagerAdapter(getSupportFragmentManager()) adapter.addPage(BookmarkListFragment.newFragment(entryInfo.bookmarkList), getString(R.string.entry_info_all_bookmarks)) subscriptions.add(Observable.from(entryInfo.bookmarkList) .filter { bookmark -> EntryInfoFunc.hasComment(bookmark) } .toList() .subscribe { commentList -> commentCountTextView.setText(commentList.size().toString()) adapter.addPage(BookmarkListFragment.newFragment(commentList), getString(R.string.entry_info_comments)) AccountDAO.get()?.let { adapter.addPage(RegisterBookmarkFragment.newFragment(entryInfo.url), getString(R.string.entry_info_register_bookmark)) } commentsViewPager.setAdapter(adapter) commentsViewPager.setCurrentItem(1) commentsViewPager.setOffscreenPageLimit(2) tabs.setViewPager(commentsViewPager) }) }, { Toast.makeText(getApplicationContext(), R.string.network_error, Toast.LENGTH_SHORT).show() })

Slide 28

Slide 28 text

もっとKotlinらしくしてみる リスト操作を高階関数に書き換える for (i in list) { if (i % 2 == 0) { System.out.print(i) } } list.filter { i -> i % 2 == 0 } .forEach { i -> System.out.print(i) } ↓

Slide 29

Slide 29 text

もっとKotlinらしくしてみる Callback用の自前Interface(trait)も public trait TestCallback { public fun onSuccess(callback: Int) } public fun request(callback: TestCallback) { callback.onSuccess(200) } // 呼び出し元 test.request(object : Test.TestCallback { override fun onSuccess(callback: Int) { } })

Slide 30

Slide 30 text

もっとKotlinらしくしてみる 高階関数で直接処理を受け取ればシンプルに出来ます public fun request(onSuccess: (callback: Int) -> Unit) { onSuccess(200) } // 呼び出し元 test.request { callback -> }

Slide 31

Slide 31 text

まとめ

Slide 32

Slide 32 text

まとめ ● Kotlin置換+αの対応だけで3500行→3000行くらいにコード をスリム化! ● Javaで設計されたコードの移行はわりと大変だった     (単純置換だけだとかえって読みにくいコードになるかも) ● とはいえAndroidアプリの開発をガッツリKotlinでやる事自体 の不便さや辛さはほとんど感じなかった           (ライブラリなどもそのまま使えたよ) ● むしろ一度Kotlinを味わうとAndroidJavaでリスナーや   リスト操作を書くのが辛くなる

Slide 33

Slide 33 text

まとめ ● 個人開発の新規アプリや期間限定の出し切りアプリなどであれ ばKotlinは現実的な選択肢では ● 個人開発の既存アプリを書き換えるべきかはモチベーション  次第だけどメリットは十分あると思う ● 業務で導入する場合、標準じゃない技術に依存するリスクを  メリットが上回るかは………

Slide 34

Slide 34 text

まとめ ● 詳しい知見はコードをご確認ください https://github.com/kirimin/mitsumine

Slide 35

Slide 35 text

誰か業務で使った実績 でっち上げてくれ!!!!

Slide 36

Slide 36 text

ご清聴ありがとうございました!