$30 off During Our Annual Pro Sale. View Details »

start from Convert to Kotlin

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/

mochico

August 25, 2018
Tweet

More Decks by mochico

Other Decks in Programming

Transcript

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

    View Slide

  2. About me
    mochico(@_mochicon_)
    Android / Java / Kotlin
    Techbooster / 技術書典

    View Slide

  3. みなさんKotlin愛でてますか?

    View Slide

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

    View Slide


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

    View Slide

  6. 解決できる

    View Slide

  7. で!

    View Slide

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

    View Slide

  9. Kotlinのよいところ
    • 静的型付き⾔語で型安全!
    • Null安全!
    • 副作⽤のない関数型で書けるのでエンバグしにくい
    • ラムダでシンプルに書ける
    • APIが豊富でやりたいことが⾃由に表現できる
    • Javaとの完全互換

    View Slide

  10. 本当に?

    View Slide

  11. ⾃分のKotlin導⼊遍歴
    • 開始3年のサービス -> Testに導⼊
    • 開始1年のサービス -> 新しく作る部分に導⼊
    • 開始5年のサービス -> 古い部分を積極的に置き換え

    View Slide

  12. Kotlinを積極的に導⼊しなかったケース
    • Javaのコードで不⾜がない
    • 変更の予定がない(少ない)
    • Javaでなければならない理由がある

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  16. Convert Java File
    to Kotlin File

    View Slide

  17. Convert Java File to Kotlin File

    View Slide

  18. SampleApp

    View Slide

  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);
    }
    }

    View Slide

  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 CREATOR = new Creator() {
    @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

    View Slide

  21. Androidアプリで便利なKotlinの機能
    • プロパティ
    • Null安全
    • data class
    • Kotlin Android Extension
    • イミュータブル

    View Slide

  22. Convertしたコードから
    もっとKotlinしてみよう!

    View Slide

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

    View Slide

  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 CREATOR = new Creator() {
    @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

    View Slide

  25. Convert to Kotlin !

    View Slide

  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 = object : Parcelable.Creator {
    public override fun createFromParcel(source: Parcel): KotlinItem {
    return KotlinItem(source)
    }
    public override fun newArray(size: Int): Array {
    return arrayOfNulls(size)
    }
    }
    }
    }
    Item.kt

    View Slide

  27. プロパティ
    • Kotlinではフィールドとアクセサメソッドの組合せ
    • プロパティにアクセスすると実際にはアクセサが使
    ⽤される

    View Slide

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

    View Slide

  29. Decompile

    View Slide

  30. Decompile

    View Slide

  31. Decompile

    View Slide

  32. Decompile

    View Slide

  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 = object : Parcelable.Creator {
    public override fun createFromParcel(source: Parcel): KotlinItem {
    return KotlinItem(source)
    }
    public override fun newArray(size: Int): Array {
    return arrayOfNulls(size)
    }
    }
    }
    }
    Item.kt

    View Slide

  34. プライマリコンストラクタと
    デフォルト引数

    View Slide

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

    View Slide

  36. デフォルト引数
    class KotlinItem(
    var id: Int = 0,
    var name: String? = null
    )
    val item = KotlinItem(1)

    View Slide

  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 = object : Parcelable.Creator
    {
    public override fun createFromParcel(source: Parcel): KotlinItem {
    return KotlinItem(source)
    }
    public override fun newArray(size: Int): Array {
    return arrayOfNulls(size)
    }
    }
    }
    }
    Item.kt

    View Slide

  38. data class

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  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 = object : Parcelable.Creator {
    public override fun createFromParcel(source: Parcel): KotlinItem {
    return KotlinItem(source)
    }
    public override fun newArray(size: Int): Array {
    return arrayOfNulls(size)
    }
    }
    }
    }

    View Slide

  44. Parcelable

    View Slide

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

    View Slide

  46. @Parcelize
    @Parcelize
    data class Item(
    var id: Int = 0,
    var name: String? = null
    ) : Parcelable

    View Slide

  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());
    }
    }

    View Slide

  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)
    }
    }

    View Slide

  49. 安全キャスト
    • キャストできない場合にnullを返す

    View Slide

  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)
    }
    }

    View Slide

  51. データクラスをつかう

    View Slide

  52. ぬるぽ

    View Slide

  53. ガッ!!

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  57. Null許容型にアクセスする

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  61. Smart cast
    val item: Item? = null
    if (item == null) {
    return
    }
    item.id
    item.name

    View Slide

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

    View Slide

  63. Nullable in Test

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  67. ActivityをKotlinに書き換える

    View Slide

  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);
    }
    }

    View Slide

  69. Convert to Kotlin !

    View Slide

  70. Converted MainActivity.kt

    View Slide

  71. Converted MainActivity.kt

    View Slide

  72. 可視修飾⼦

    View Slide

  73. 可視修飾⼦
    • public:どこからも参照可能
    • internal :モジュール内から
    のみ参照可能
    • private:クラス内からのみ
    参照可能
    • なし:public
    • public:どこからも参照可能
    • protected:現在のクラスとサ
    ブクラスからアクセスできる
    • private:クラス内からのみ参
    照可能
    • なし:同じパッケージのクラ
    スから参照可能
    Java Kotlin

    View Slide

  74. Converted MainActivity.kt

    View Slide

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

    View Slide

  76. var & val
    var variableNumber = 1
    variableNumber = 2 // OK
    val valueNumber = 1
    valueNumber = 2 // ίϯύΠϧΤϥʔ

    View Slide

  77. private var recyclerView: RecyclerView? = null

    View Slide

  78. private var recyclerView: RecyclerView? = null

    View Slide

  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(R.id.recycler_view)

    View Slide

  80. by lazy
    private val recyclerView: RecyclerView by lazy {
    findViewById(R.id.recycler_view)
    }

    View Slide

  81. MainActivity.kt

    View Slide

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

    View Slide

  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(R.id.toolbar)
    setSupportActionBar(toolbar)
    findViewById(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
    }
    }

    View Slide

  84. Convertしたコードの
    おさらい

    View Slide

  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 CREATOR = new Parcelable.Creator() {
    @Override
    public JavaItem createFromParcel(Parcel source) {
    return new JavaItem(source);
    }
    @Override
    public JavaItem[] newArray(int size) {
    return new JavaItem[size];
    }
    };
    }

    View Slide

  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
    }
    }

    View Slide

  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);
    }
    }

    View Slide

  88. Converted MainActivity.kt

    View Slide

  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(R.id.toolbar)
    setSupportActionBar(toolbar)
    findViewById(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
    }
    }

    View Slide

  90. Convert to Kotlin
    して終わりではない

    View Slide

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

    View Slide

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

    View Slide

  93. Enjoy Kotlin!

    View Slide

  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)

    View Slide