Pro Yearly is on sale from $80 to $50! »

start from Convert to Kotlin

6eecc52120fba9d87d982684211182ec?s=47 mochico
August 25, 2018

start from Convert to Kotlin

2018.8.25 Kotlin Fest 2018
初学者向けにJavaでつくられたAndroidアプリでConvertしたKotlinコードからよりよいKotlinコードの書き方を考えてみるセッションです。

サンプルコード: https://github.com/mochico/StartFromConvertToKotlinSample/

6eecc52120fba9d87d982684211182ec?s=128

mochico

August 25, 2018
Tweet

Transcript

  1. start from Convert to Kotlin @mochico 2018.8.25 Kotlin Fest 2018

  2. About me mochico(@_mochicon_) Android / Java / Kotlin Techbooster /

    技術書典
  3. みなさんKotlin愛でてますか?

  4. 想定プロジェクト • サービス開始から数年たったコード • 無数のデータクラス • テストはほとんどナシ • そのときどきのベスト・プラクティス

  5. メンバーが増えていろんなコードスタイルの⼈がいる ボイラープレートなコードが多い 引数が多くて呼び出しかたが複雑

  6. 解決できる

  7. で!

  8. 今⽇の⽬的 • まだあまりKotlinしてない⼈:帰ったらKotlinして みたくなる • ちょっとKotlinしてる⼈:もっとKotlinの良さを享 受する • だいぶKotlinしてる⼈:もっといい⽅法があったら 教えてください!

  9. Kotlinのよいところ • 静的型付き⾔語で型安全! • Null安全! • 副作⽤のない関数型で書けるのでエンバグしにくい • ラムダでシンプルに書ける •

    APIが豊富でやりたいことが⾃由に表現できる • Javaとの完全互換
  10. 本当に?

  11. ⾃分のKotlin導⼊遍歴 • 開始3年のサービス -> Testに導⼊ • 開始1年のサービス -> 新しく作る部分に導⼊ •

    開始5年のサービス -> 古い部分を積極的に置き換え
  12. Kotlinを積極的に導⼊しなかったケース • Javaのコードで不⾜がない • 変更の予定がない(少ない) • Javaでなければならない理由がある

  13. Kotlinにしない理由がない

  14. もっとAndroidアプリで上⼿ にKotlinするには?

  15. Java製アプリにKotlinを導⼊する 1. テストを書く 2. 新しく書く部分で導⼊する 3. 既存のJavaコードをConvert

  16. Convert Java File to Kotlin File

  17. Convert Java File to Kotlin File

  18. SampleApp

  19. MainActivity.java public class JavaMainActivity extends AppCompatActivity { RecyclerView recyclerView; ItemAdapter

    adapter = new ItemAdapter(); @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Toolbar toolbar = findViewById(R.id.toolbar); setSupportActionBar(toolbar); findViewById(R.id.fab).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { Snackbar.make(view, "tapped!", Snackbar.LENGTH_LONG) .setAction("Action", null).show(); } }); recyclerView = findViewById(R.id.recycler_view); recyclerView.setAdapter(adapter); } }
  20. public class JavaItem implements Parcelable { public JavaItem(int id, String

    name) { this.id = id; this.name = name; } private int id; private String name; public int getId() { return id; } public String getName() { return name; } public void setId(int id) { this.id = id; } public void setName(String name) { this.name = name; } private JavaItem(Parcel source) { id = source.readInt(); name = source.readString(); } @Override public int describeContents() { return 0; } @Override public void writeToParcel(Parcel dest, int atags) { dest.writeInt(id); dest.writeString(name); } public static final Creator<JavaItem> CREATOR = new Creator<JavaItem>() { @Override public JavaItem createFromParcel(Parcel source) { return new JavaItem(source); } @Override public JavaItem[] newArray(int size) { return new JavaItem[size]; } }; public boolean isSameItem(Object obj) { return obj instanceof JavaItem && ((JavaItem) obj).getId() == this.id; } } Item.java
  21. Androidアプリで便利なKotlinの機能 • プロパティ • Null安全 • data class • Kotlin

    Android Extension • イミュータブル
  22. Convertしたコードから もっとKotlinしてみよう!

  23. データクラスを Kotlinで書き換える

  24. public class JavaItem implements Parcelable { public JavaItem(int id, String

    name) { this.id = id; this.name = name; } private int id; private String name; public int getId() { return id; } public String getName() { return name; } public void setId(int id) { this.id = id; } public void setName(String name) { this.name = name; } private JavaItem(Parcel source) { id = source.readInt(); name = source.readString(); } @Override public int describeContents() { return 0; } @Override public void writeToParcel(Parcel dest, int atags) { dest.writeInt(id); dest.writeString(name); } public static final Creator<JavaItem> CREATOR = new Creator<JavaItem>() { @Override public JavaItem createFromParcel(Parcel source) { return new JavaItem(source); } @Override public JavaItem[] newArray(int size) { return new JavaItem[size]; } }; public boolean isSameItem(Object obj) { return obj instanceof JavaItem && ((JavaItem) obj).getId() == this.id; } } Item.java
  25. Convert to Kotlin !

  26. class KotlinItem : Parcelable { var id: Int = 0

    var name: String? = null constructor(id: Int, name: String) { this.id = id this.name = name } private constructor(source: Parcel) { id = source.readInt() name = source.readString() } public override fun describeContents(): Int { return 0 } public override fun writeToParcel(dest: Parcel, atags: Int) { dest.writeInt(id) dest.writeString(name) } fun isSameItem(obj: Any): Boolean { return (obj is KotlinItem && (obj as KotlinItem).id == this.id) } companion object { val CREATOR: Parcelable.Creator<KotlinItem> = object : Parcelable.Creator<KotlinItem> { public override fun createFromParcel(source: Parcel): KotlinItem { return KotlinItem(source) } public override fun newArray(size: Int): Array<KotlinItem> { return arrayOfNulls<KotlinItem>(size) } } } } Item.kt
  27. プロパティ • Kotlinではフィールドとアクセサメソッドの組合せ • プロパティにアクセスすると実際にはアクセサが使 ⽤される

  28. 実際にはどんなコードになっているのか?

  29. Decompile

  30. Decompile

  31. Decompile

  32. Decompile

  33. class KotlinItem : Parcelable { var id: Int = 0

    var name: String? = null constructor(id: Int, name: String) { this.id = id this.name = name } private constructor(source: Parcel) { id = source.readInt() name = source.readString() } public override fun describeContents(): Int { return 0 } public override fun writeToParcel(dest: Parcel, atags: Int) { dest.writeInt(id) dest.writeString(name) } fun isSameItem(obj: Any): Boolean { return (obj is KotlinItem && (obj as KotlinItem).id == this.id) } companion object { val CREATOR: Parcelable.Creator<KotlinItem> = object : Parcelable.Creator<KotlinItem> { public override fun createFromParcel(source: Parcel): KotlinItem { return KotlinItem(source) } public override fun newArray(size: Int): Array<KotlinItem> { return arrayOfNulls<KotlinItem>(size) } } } } Item.kt
  34. プライマリコンストラクタと デフォルト引数

  35. プライマリコンストラクタ class KotlinItem( var id: Int, var name: String? )

  36. デフォルト引数 class KotlinItem( var id: Int = 0, var name:

    String? = null ) val item = KotlinItem(1)
  37. class KotlinItem( var id: Int = 0, var name: String?

    = null ) : Parcelable { private constructor(source: Parcel) : this(source.readInt(),source.readString()) public override fun describeContents(): Int { return 0 } public override fun writeToParcel(dest: Parcel, atags: Int) { dest.writeInt(id) dest.writeString(name) } fun isSameItem(obj: Any): Boolean { return (obj is KotlinItem && (obj as KotlinItem).id == this.id) } companion object { val CREATOR: Parcelable.Creator<KotlinItem> = object : Parcelable.Creator<KotlinItem> { public override fun createFromParcel(source: Parcel): KotlinItem { return KotlinItem(source) } public override fun newArray(size: Int): Array<KotlinItem> { return arrayOfNulls<KotlinItem>(size) } } } } Item.kt
  38. data class

  39. data class in Kotlin data class Item (val id: Int,

    val name: String)
  40. data classで⾃動⽣成されるメソッド • equals • hashCode • toString • copy

  41. Item.java#toString() Item.toString: mochico.example.com.start.from.convert.to.kotlin.models.JavaItem@98824bc

  42. Item.kt#toString() Item.toString: Item(id=1, name=name)

  43. Item.kt data class KotlinItem( var id: Int = 0, var

    name: String? = null ) : Parcelable { private constructor(source: Parcel) : this(source.readInt(), source.readString()) public override fun describeContents(): Int { return 0 } public override fun writeToParcel(dest: Parcel, atags: Int) { dest.writeInt(id) dest.writeString(name) } fun isSameItem(obj: Any): Boolean { return (obj is KotlinItem && (obj as KotlinItem).id == this.id) } companion object { val CREATOR: Parcelable.Creator<KotlinItem> = object : Parcelable.Creator<KotlinItem> { public override fun createFromParcel(source: Parcel): KotlinItem { return KotlinItem(source) } public override fun newArray(size: Int): Array<KotlinItem> { return arrayOfNulls<KotlinItem>(size) } } } }
  44. Parcelable

  45. Kotlin Android Extensions apply plugin: 'kotlin-android-extensions' androidExtensions { experimental =

    true }
  46. @Parcelize @Parcelize data class Item( var id: Int = 0,

    var name: String? = null ) : Parcelable
  47. Decompile @Parcelize public static final android.os.Parcelable.Creator CREATOR = new Item.Creator();

    public final int describeContents() { return 0; } public final void writeToParcel(@NotNull Parcel parcel, int flags) { Intrinsics.checkParameterIsNotNull(parcel, "parcel"); parcel.writeInt(this.id); parcel.writeString(this.name); } @Metadata( mv = {1, 1, 10}, bv = {1, 0, 2}, k = 3 ) public static class Creator implements android.os.Parcelable.Creator { @NotNull public final Object[] newArray(int size) { return new Item[size]; } @NotNull public final Object createFromParcel(@NotNull Parcel in) { Intrinsics.checkParameterIsNotNull(in, "in"); return new Item(in.readInt(), in.readString()); } }
  48. Item.kt @Parcelize data class Item( var id: Int = 0,

    var name: String? = null ) : Parcelable { fun isSameItem(obj: Any): Boolean { return (obj is KotlinItem && (obj as KotlinItem).id == this.id) } }
  49. 安全キャスト • キャストできない場合にnullを返す

  50. Item.kt @Parcelize data class KotlinItem( var id: Int = 0,

    var name: String? = null ) : Parcelable { fun isSameItem(obj: Any): Boolean { return ((obj as? KotlinItem)?.id == this.id) } }
  51. データクラスをつかう

  52. ぬるぽ

  53. ガッ!!

  54. Null安全 ? • 予期しないNullはユーザーのクラッシュに直結する • nullが来る可能性がある場⾯が多くある • どの場⾯でnullが来るかを明確にしNPEの可能性を 下げる

  55. Null許容型とNull⾮許容型

  56. プラットフォーム型 ! • Javaの型はNull許容についての情報がないものとし て扱う • 警告などで⾒られるが、コードで定義はできない • @Nullable, @Nonull

    アノテーションをつけたとき はNull許容型,⾮許容型として扱う
  57. Null許容型にアクセスする

  58. ⾮null表明 !! val item: Item? = null item!!.id item.name

  59. ⾮null表明 !! val item: Item? = null item!!.id item.name

  60. Nullを安全に回避する • smart cast • ?: エルビス演算⼦ • ?.

  61. Smart cast val item: Item? = null if (item ==

    null) { return } item.id item.name
  62. エルビス演算⼦ val item: Item? = null item ?: return item.id

    item.name
  63. Nullable in Test

  64. ?. val javaItem: Item? = null javaItem?.id javaItem?.name

  65. Nullを安全に回避する • どれを使⽤するかはケースバイケース • !! が本当に必要なことはあまりない

  66. named argument val item1 = Item(id = 1, name =

    "name") val item2 = Item(name = "name", id = 1)
  67. ActivityをKotlinに書き換える

  68. MainActivity.java public class JavaMainActivity extends AppCompatActivity { RecyclerView recyclerView; ItemAdapter

    adapter = new ItemAdapter(); @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Toolbar toolbar = findViewById(R.id.toolbar); setSupportActionBar(toolbar); findViewById(R.id.fab).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { Snackbar.make(view, "tapped!", Snackbar.LENGTH_LONG) .setAction("Action", null).show(); } }); recyclerView = findViewById(R.id.recycler_view); recyclerView.setAdapter(adapter); } }
  69. Convert to Kotlin !

  70. Converted MainActivity.kt

  71. Converted MainActivity.kt

  72. 可視修飾⼦

  73. 可視修飾⼦ • public:どこからも参照可能 • internal :モジュール内から のみ参照可能 • private:クラス内からのみ 参照可能

    • なし:public • public:どこからも参照可能 • protected:現在のクラスとサ ブクラスからアクセスできる • private:クラス内からのみ参 照可能 • なし:同じパッケージのクラ スから参照可能 Java Kotlin
  74. Converted MainActivity.kt

  75. どこでどう初期化するか?

  76. var & val var variableNumber = 1 variableNumber = 2

    // OK val valueNumber = 1 valueNumber = 2 // ίϯύΠϧΤϥʔ
  77. private var recyclerView: RecyclerView? = null

  78. private var recyclerView: RecyclerView? = null

  79. lateinit class KotlinMainActivity : AppCompatActivity() { private lateinit var recyclerView:

    RecyclerView private var adapter = ItemAdapter() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) recyclerView = findViewById<RecyclerView>(R.id.recycler_view)
  80. by lazy private val recyclerView: RecyclerView by lazy { findViewById<RecyclerView>(R.id.recycler_view)

    }
  81. MainActivity.kt

  82. ラムダ • 他の関数に渡すことができる無名関数 • AndroidではOnClickListenerなどでよくみる

  83. MainActivity.kt class MainActivity : AppCompatActivity() { private lateinit var recyclerView:

    RecyclerView private var adapter = ItemAdapter() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) val toolbar = findViewById<Toolbar>(R.id.toolbar) setSupportActionBar(toolbar) findViewById<View>(R.id.fab).setOnClickListener { view -> Snackbar.make(view, "tapped!", Snackbar.LENGTH_LONG) .setAction("Action", null).show() } recyclerView = findViewById(R.id.recycler_view) recyclerView.adapter = adapter } }
  84. Convertしたコードの おさらい

  85. Java entity class public class JavaItem implements Parcelable { public

    JavaItem(int id, String name) { this.id = id; this.name = name; } private int id; private String name; public int getId() { return id; } public String getName() { return name; } public void setId(int id) { this.id = id; } public void setName(String name) { this.name = name; } private JavaItem(Parcel source) { id = source.readInt(); name = source.readString(); } @Override public int describeContents() { return 0; } @Override public void writeToParcel(Parcel dest, int atags) { dest.writeInt(id); dest.writeString(name); } public static final Parcelable.Creator<JavaItem> CREATOR = new Parcelable.Creator<JavaItem>() { @Override public JavaItem createFromParcel(Parcel source) { return new JavaItem(source); } @Override public JavaItem[] newArray(int size) { return new JavaItem[size]; } }; }
  86. Kotlin data class @Parcelize data class Item( var id: Int

    = 0, var name: String? = null ) : Parcelable { fun isSameItem(obj: Any): Boolean { return (obj as KotlinItem).id == this.id } }
  87. MainActivity.java public class JavaMainActivity extends AppCompatActivity { RecyclerView recyclerView; ItemAdapter

    adapter = new ItemAdapter(); @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Toolbar toolbar = findViewById(R.id.toolbar); setSupportActionBar(toolbar); findViewById(R.id.fab).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { Snackbar.make(view, "tapped!", Snackbar.LENGTH_LONG) .setAction("Action", null).show(); } }); recyclerView = findViewById(R.id.recycler_view); recyclerView.setAdapter(adapter); } }
  88. Converted MainActivity.kt

  89. MainActivity.kt class MainActivity : AppCompatActivity() { private lateinit var recyclerView:

    RecyclerView private var adapter = ItemAdapter() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) val toolbar = findViewById<Toolbar>(R.id.toolbar) setSupportActionBar(toolbar) findViewById<View>(R.id.fab).setOnClickListener { view -> Snackbar.make(view, "tapped!", Snackbar.LENGTH_LONG) .setAction("Action", null).show() } recyclerView = findViewById(R.id.recycler_view) recyclerView.adapter = adapter } }
  90. Convert to Kotlin して終わりではない

  91. もっとよいKotlinコードを書く • Convert to Kotlinやlintは⽇々進化している • 「こんな⾵に書けたら便利なのに」は⼤体ある • 「Kotlinらしく」だけでなく「読みやすいコード」 「壊れないコード」を⽬指す

  92. Kotlin移⾏で気をつけること • Javaを全く意識しなくてよいわけではない • 特にNull許容型

  93. Enjoy Kotlin!

  94. ref. • Getting started with Android and Kotlin - Kotlin

    Programming Language : https://kotlinlang.org/ docs/tutorials/kotlin-android.html • Get Started with Kotlin on Android | Android Developers : https://developer.android.com/kotlin/ get-started • [Kotlinイン・アクション | マイナビブックス](https:// book.mynavi.jp/ec/products/detail/id=78137)