Slide 1

Slide 1 text

決戦Kotlinコンバート MobileAct osaka #4 @pg0084

Slide 2

Slide 2 text

自己紹介 ・オオハシタクヤ @pg0084 ・フェンリルに所属 Androidエンジニア ・趣味はマクドナルド

Slide 3

Slide 3 text

2月8日DroidKaigi2018 最高カンファレンスでした。

Slide 4

Slide 4 text

しかし・・・。

Slide 5

Slide 5 text

Kotlinを知らない Androidアプリエンジニアに 人権がない!!

Slide 6

Slide 6 text

ということでJavaのプロジェクトを Kotlinコンバートしてみた

Slide 7

Slide 7 text

Javaファイル数約560

Slide 8

Slide 8 text

今回は約500ファイルをコンバート 将来的には全Kotlin化を目指します。

Slide 9

Slide 9 text

目次 1. kaptでビルドが通らない?Lombokの罠! 2. IcepickがKotlinに対応していない件につきまして 3. Interfaceの実装方法がバラバラだと困るSAM変換 4.値が更新されない?by lazyでfindVIew 5.コメントが入れ子にできる仕様で30秒思考停止 6.気の利いた謎のコンバート

Slide 10

Slide 10 text

Kaptでビルドが通らない? Lombokの罠!

Slide 11

Slide 11 text

Lombokとは アノテーションを付けるだけで、 getterやsetter、コンストラクターまで生 成してくれる便利ライブラリ。 @Getter @Setter @Data @AllArgsConstructor など

Slide 12

Slide 12 text

kaptとLombok の相性が悪いらしく Lombok で実装しているクラスのメンバはprivateなのでSetter、Getter にアクセスできないと言われ、ビルドエラーに。 ※この段階ではまだ1ファイルもコンバートしていません。 原因

Slide 13

Slide 13 text

とった策 将来的に全Kotlin化を想定しているのでLombokのアノテーションを全て外す Lombokを使っているクラスだけで300近くあるから手動は大変! delombokが便利! lombok.jarを使って以下のようなコマンドを打つだけ! 念のために別ディレクトリにdelombokされたファイルを出力します。 java -jar lombok.jar delombok -f pretty {対象ディレクトリ} -d {出力先ディレクトリ}

Slide 14

Slide 14 text

Icepickが対応していない件につきまして

Slide 15

Slide 15 text

Icepickとは AndroidではActivityなどの破棄により、入力した値が破棄される。 状態の保存/復元をサポートするアノテーションライブラリ。 保持したい変数に@Stateをつけ、saveInstanceStateと restoreInstanceStateを呼んであげるだけ! 超便利なライブラリ。 Kotlinだと@JvmField アノテーションをつけないと動作しない! setter/getterではないField変数をそのまま公開しないといけなくなる ので何とも言えない気持ちになる

Slide 16

Slide 16 text

Icepickをandroid-stateに置き換え @Override public void onCreate(Bundle savedInstanceState) { Icepick.restoreInstanceState(this, savedInstanceState); } @Override public void onSaveInstanceState(Bundle outState) { Icepick.saveInstanceState(this, outState); } Before

Slide 17

Slide 17 text

Icepickをandroid-stateに置き換え override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) } override fun onSaveInstanceState(outState: Bundle?) { StateSaver.saveInstanceState(this, outState); } After

Slide 18

Slide 18 text

値が更新されない? by lazyでfindview

Slide 19

Slide 19 text

例えばこんなの val myTextView: TextView by lazy{ findViewById(R.id.my_text)} 参考:https://qiita.com/takahirom/items/49e18ec7084bb3b1937e 遅延初期化で利用できます。 が・・・。 バックスタックで戻った際やTabLayoutなどで開きなおした後に値を差し変 たいのに見た目が変わらない。

Slide 20

Slide 20 text

対策 lateinit var myTextView : TextView … override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) myButton = findViewById(R.id.my_text) 参考:https://qiita.com/takahirom/items/49e18ec7084bb3b1937e lateinitで実装するのが一般的のようです。

Slide 21

Slide 21 text

実装方法がバラバラだと困るinterfaceとSAM変換

Slide 22

Slide 22 text

SAM変換って? SAMとはSingle Abstract Methodの略で、SAMインターフェースは、 一つだけ抽象メソッドをもつインターフェースです。 button.setOnClickListener(object: View.OnClickListener { public override fun onClick(v: View) { Log.v(TAG, "clicked") } });} button.setOnClickListener{ v -> Log.v(TAG, "clicked") } 下のように記述できる

Slide 23

Slide 23 text

Kotlin化が進んでくると class MyButton (context: Context) : android.support.v7.widget.AppCompatButton(context) { private var listener: MyBtClickListener? = null init { setOnClickListener { v -> listener?.onMyButtonClick(v.toString()) } } internal interface MyBtClickListener { fun onMyButtonClick(v: String) } //リスナーを追加するメソッド fun set MyBtClickListener(listener: MyBtClickListener) { this.listener = listener } } こんなクラスがあるとします。

Slide 24

Slide 24 text

呼び出し方 Interfaceも呼び出す側もKotlinの場合 myButton.setMyListener{v->Log.d("hogehoge", v.toString())}. ビルドエラーになります。

Slide 25

Slide 25 text

こういう書き方にできる private var listener:((v: String)->Unit)? = null init { setOnClickListener { v -> listener?.invoke(v.toString()) } } myButton.setMyListener{v->Log.d("hogehoge", v.toString())}. ビルドが通って思った通りに動きます。

Slide 26

Slide 26 text

実装の仕方がバラバラだとハマる可能性 同じinterfaceを実装している場合、2パターンの実装している場合もあ る。 コンバートした後にinterfaceのまま残すか、関数型にして実装方法を 統一しなくてはいけない。 class MainActivity : AppCompatActivity(),MyButton.MyBtClickListener{ override fun onMyButtonClick(v: View) { Log.d("hogehoge", v.toString()) } myButton.setMyListener{v->Log.d("hogehoge", v.toString())}.

Slide 27

Slide 27 text

コメントが入れ子にできる仕様で 30秒くらいフリーズした話

Slide 28

Slide 28 text

Kotlinではブロックコメントが入れ子にできる 入れ子になっていることにぱっと見気がつかない コメントで /*.XML とか書いてあったらコンバート後に間違い探しが発生!30秒思考 停止。 /** * /src/layout/*.xml */ Class MainActivity: AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) }

Slide 29

Slide 29 text

気の利いた謎のコンバート

Slide 30

Slide 30 text

例えばこんなの class Hoge{ private String param1; private String param2; public Hoge(String parm1, String param2){ this.param1 = param1; this.param2 = param2; } } Hoge hoge = new Hoge(param1, null); Javaであえてnullを渡している処理が書いてあった。 そもそもnullをあえて渡しているのもどうかと思う人も多いと思いますが、 コンバートした結果

Slide 31

Slide 31 text

コンバート結果 class Hoge(val parm1: String, val param2: String) { } val hoge = Hoge(param1, null!!) !! を付与ることでNotNullに変換することができる

Slide 32

Slide 32 text

null!!

Slide 33

Slide 33 text

まとめ Kotlinコンバートは思ったより手間がかかる

Slide 34

Slide 34 text

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