Upgrade to Pro — share decks privately, control downloads, hide ads and more …

個人開発AndroidアプリをKotlinにガチ移行してみた話

 個人開発AndroidアプリをKotlinにガチ移行してみた話

D96c7e61d2f394f1d0af66945181a230?s=128

きりみん

June 05, 2015
Tweet

More Decks by きりみん

Other Decks in Technology

Transcript

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

  2. 自己紹介 @kirimin • Androidアプリ開発者 (ジャバ) • エンジニア4年目 フリーランス2年目 • 現在は圧倒的当事者意識で某社の

    Androidアプリ開発を手伝っています
  3. 自己紹介

  4. None
  5. 今、Androidで Kotlinがアツい!!!!

  6. なぜAndroidでKotlin?

  7. なぜAndroidでKotlin?(おさらい) • AndroidではJava8が当分使えなそう • Kotlinならラムダ式もパイプラインでのリスト操作も書ける • RxAndroidとも相性が良い • Android StudioならIDEのサポートが効きすぐに使える

    • 学習コストも低く、静的で安全志向な言語仕様は    Androidエンジニアに丁度いい(主観) • Scalaはいろんな意味で人類にはまだ早い
  8. KotlinでAndroid開発がしたい!!!

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

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

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

  12. mitsumine

  13. mitsumine • 約3500行 • 54クラス • 使用しているライブラリ • RxAndroid •

    Volley • ActiveAndroid • Picasso      など...
  14. やったこと • Kotlin利用準備 • JavaクラスをひたすらKotlin化 • Kotlin Android Extensionsを導入 •

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

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

  17. やったこと • Kotlin利用準備 • JavaクラスをひたすらKotlin化 • Kotlin Android Extensionsを導入 •

    もっとKotlinらしくしてみる
  18. JavaクラスをひたすらKotlin化 「Convert Java to Kotlin File」でひたすらポチポチ…

  19. JavaクラスをひたすらKotlin化 「Convert Java to Kotlin File」でひたすらポチポチ… • クラス数が多いと結構だるい… (一斉変換も出来るけど) • 大抵は自動変換しただけでちゃんと動く

    • 時々コンパイルエラーが出る • 時々実行時エラーが出る • 時々変な変換されて処理が消える • 1クラス変換する度にDiffって動作確認した方がハマらない(戒め)
  20. JavaクラスをひたすらKotlin化 変換時に遭遇したエラーの思い出 • Null許容型とNull非許容型で型エラーが出てる →型に?を付けてNull許容型に変えたりして頑張ろう • overrideメソッドのsuperでエラーが出てる →super<Fragment>のようにどのスーパークラスを呼ぶのか 明示しよう •

    CustomViewのコンストラクタでエラーが出てる  →class xxxView: View()の()を消してsecondary constructor の構文を使おう
  21. • Kotlin利用準備 • JavaクラスをひたすらKotlin化 • Kotlin Android Extensionsを導入 • もっとKotlinらしくしてみる

    やったこと
  22. Kotlin Android Extensionsを導入 Kotlin Android Extensionsとは • KotlinのAndroid用公式ライブラリ(プラグイン) • findViewByIdを葬り去るためのライブラリ

    • Activityクラスのimport文でActivityとレイアウトファイルを紐 付けるだけでActivityにViewのプロパティを生やしてくれる • Butter Knifeの@InjectViewすら必要ない版のようなイメージ • 手軽に使えてめちゃくちゃ便利
  23. Kotlin Android Extensionsを導入  import kotlinx.android.synthetic.<layout>.* ActivityやFramentにレイアウトファイルを紐付ける。  userNameTextView.setText(“test”); Activityのプロパティになるのでそのまま参照出来る。  getView().userNameTextView.setText(“test”); Fragmentの場合はgetView()で取得出来るルートのViewに生える。

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

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

    { } }) textView.setOnClickListener { v -> } ↓
  26. もっとKotlinらしくしてみる 匿名クラスのままKotlinに変換されたRxJavaの処理も subscriptions.add(EntryInfoApi.request(RequestQueueSingleton.get(getApplicationContext()), url) .subscribeOn(Schedulers.newThread()) .observeOn(AndroidSchedulers.mainThread()) .map<EntryInfo>(EntryInfoFunc.mapToEntryInfo()) .filter(object : Func1<EntryInfo,

    Boolean> { override fun call(entryInfo: EntryInfo?): Boolean? { return entryInfo == null } }) .subscribe(object : Action1<EntryInfo> { 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<Bookmark>(entryInfo.getBookmarkList()) .filter(EntryInfoFunc.hasComment()) .toList() .subscribe(object : Action1<List<Bookmark>> { override fun call(commentList: List<Bookmark>) { 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<Throwable> { override fun call(throwable: Throwable) { Toast.makeText(getApplicationContext(), R.string.network_error, Toast.LENGTH_SHORT).show() }
  27. もっと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<Bookmark>(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() })
  28. もっと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) } ↓
  29. もっと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) { } })
  30. もっとKotlinらしくしてみる 高階関数で直接処理を受け取ればシンプルに出来ます public fun request(onSuccess: (callback: Int) -> Unit) {

    onSuccess(200) } // 呼び出し元 test.request { callback -> }
  31. まとめ

  32. まとめ • Kotlin置換+αの対応だけで3500行→3000行くらいにコード をスリム化! • Javaで設計されたコードの移行はわりと大変だった     (単純置換だけだとかえって読みにくいコードになるかも) • とはいえAndroidアプリの開発をガッツリKotlinでやる事自体 の不便さや辛さはほとんど感じなかった          

    (ライブラリなどもそのまま使えたよ) • むしろ一度Kotlinを味わうとAndroidJavaでリスナーや   リスト操作を書くのが辛くなる
  33. まとめ • 個人開発の新規アプリや期間限定の出し切りアプリなどであれ ばKotlinは現実的な選択肢では • 個人開発の既存アプリを書き換えるべきかはモチベーション  次第だけどメリットは十分あると思う • 業務で導入する場合、標準じゃない技術に依存するリスクを  メリットが上回るかは………

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

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

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