LoginSignup
34
23

More than 5 years have passed since last update.

Kotlin Parcelable について

Last updated at Posted at 2018-09-27

(2019/09/28 ライブラリについて追記しました)

はじめに

Kotlin勉強中です。
コメント、ご指摘あれば是非よろしくお願いします

動作確認環境は以下の通りです

  • Android Studio 3.2
  • Kotlin 1.2.61

Parcelable とは

AndroidでIntentにデータを詰め込む際に、オブジェクトをシリアライズする必要があります。
その際に、Parcelableインターフェースを実装することで格納することが可能になります。

Serializable との違い

個人的にはアプリ内に閉じた明示的Intentでのデータの受け渡しで、単純なデータクラスの場合はSerializableでもいいかなと思っています。
何よりも実装が楽なので。

ただし、匿名クラスやインナークラスをシリアライズする場合はちょっと面倒なので、注意が必要です。
細かい内容はこちらのブログが参考になるかと思います。

Parcelable を利用するにあたり

肝心のParcelableのメリットですが

  • Android上でのパフォーマンスに最適化されている
  • アプリ間連携に使える

という点が挙げられます。

逆にデメリットはやはり「実装がめんどくさい」というところでしょうか。。。
(簡単なクラスならIDEのオートジェネレイトで簡単に作れるので、言うほど面倒ではないですが)

そこでKotlinでParcelableを実装すれば、コード量も比較的抑えられるので良いかと思います。

実装

こちらのブログが参考になります

まずJavaで書いた場合、パラメータが二つで60行弱程度になります。

import android.os.Parcel;
import android.os.Parcelable;

public class ParcelableData implements Parcelable {
    private int id;
    private String name;

    public ParcelableData(int id, String name) {
        this.id = id;
        this.name = name;
    }

    protected ParcelableData(Parcel in) {
        id = in.readInt();
        name = in.readString();
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeInt(id);
        dest.writeString(name);
    }

    @Override
    public int describeContents() {
        return 0;
    }

    public static final Creator<ParcelableData> CREATOR = new Creator<ParcelableData>() {
        @Override
        public ParcelableData createFromParcel(Parcel in) {
            return new ParcelableData(in);
        }

        @Override
        public ParcelableData[] newArray(int size) {
            return new ParcelableData[size];
        }
    };

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }   
}

これをKotlinに自動変換し、エラーを適宜修正するだけで40行程度になります。
Javaからのコンバートではなく新規なら、data classでパラメータだけ作成した後に、Parcelableの実装をオートジェネレイトすれば概ね似たような状態になると思います。

import android.os.Parcel
import android.os.Parcelable

class ParcelableData : Parcelable {
    var id: Int = 0
    var name: String? = null

    constructor(id: Int, name: String) {
        this.id = id
        this.name = name
    }

    protected constructor(`in`: Parcel) {
        id = `in`.readInt()
        name = `in`.readString()
    }

    override fun writeToParcel(dest: Parcel, flags: Int) {
        dest.writeInt(id)
        dest.writeString(name)
    }

    override fun describeContents(): Int {
        return 0
    }

    companion object {

        @JvmField
        val CREATOR: Parcelable.Creator<ParcelableData> = object: Parcelable.Creator<ParcelableData> {
            override fun createFromParcel(`in`: Parcel): ParcelableData {
                return ParcelableData(`in`)
            }

            override fun newArray(size: Int): Array<ParcelableData?> {
                return arrayOfNulls(size)
            }
        }
    }
}

さらに、data classにして、コンストラクタ周りの整理や=で関数を省略すると30行弱まで減ります
ついでに紛らわしい予約後のinを変更

import android.os.Parcel
import android.os.Parcelable

data class ParcelableData(
        var id: Int = 0,
        var name: String? = null
) : Parcelable {

    override fun writeToParcel(dest: Parcel, flags: Int) {
        dest.writeInt(id)
        dest.writeString(name)
    }

    override fun describeContents() = 0

    companion object {

        @JvmField
        val CREATOR: Parcelable.Creator<ParcelableData> = object: Parcelable.Creator<ParcelableData> {
            override fun createFromParcel(source: Parcel): ParcelableData {
                return ParcelableData(source.readInt(), source.readString())
            }

            override fun newArray(size: Int): Array<ParcelableData?>  = arrayOfNulls(size)
        }
    }
}

あとは、好みですがスコープ関数でKotlinらしくすると完成
綺麗にまとまりました。
CREATORの警告が気になる方は@Suppress("unused")を付与してください。

import android.os.Parcel
import android.os.Parcelable

data class ParcelableData(
        var id: Int = 0,
        var name: String? = null
) : Parcelable {

    override fun writeToParcel(dest: Parcel, flags: Int) {
        dest.let {
            it.writeInt(id)
            it.writeString(name)
        }
    }

    override fun describeContents() = 0

    companion object {

        @Suppress("unused")
        @JvmField
        val CREATOR: Parcelable.Creator<ParcelableData> = object: Parcelable.Creator<ParcelableData> {
            override fun createFromParcel(source: Parcel): ParcelableData
                    = source.let { ParcelableData(it.readInt(), it.readString()) }

            override fun newArray(size: Int): Array<ParcelableData?>  = arrayOfNulls(size)
        }
    }
}

コード行数が減るのは、getter / setter がないのが大きいですが、それでも余計なものがなくなりスッキリして見えるのが良いと思います。

注意点

以下のブログにありますが、Javaでプリミティブだったintなどをオプショナルで利用する場合は注意が必要です。

具体的には以下のような場合に、writeInt()は非オプショナル型なので、nullの時は書き込まないようにしてしまうと次の変数のデータの一部(下記の場合は文字列長)が入ってしまいます。

data class ParcelableData(
        var id: Int? = null,
        var name: String? = null
) : Parcelable {

    override fun writeToParcel(dest: Parcel, flags: Int) = with(dest) {
        id?.let{ writeInt(it) }
        writeString(name)
    }

・・・

}

この場合の対処として、簡単な方法はwriteValue()に変更するか、記事に記載のようなnullか判断できる情報も一緒に含めればOKです。

data class ParcelableData(
        var id: Int? = null,
        var name: String? = null
) : Parcelable {

    constructor(source: Parcel) : this(
            if (source.readInt() == 1) source.readInt() else null,
            source.readString()
    )

    override fun writeToParcel(dest: Parcel, flags: Int) = with(dest) {
        writeInt(if (id != null) 1 else 0)
        id?.let{ writeInt(it) }
        writeString(name)
    }

・・・

}

ライブラリ

Parcelize

Kotlin 1.1.4 からAndroid Extensions pluginにParcelable Supportがexperimentalな機能として含まれています。
正式に導入するかは悩みどころかと思いますが、便利なのでぜひ使いたいところです。

実際の使用方法はこちらが参考になります。

具体的には以下の通りです。

app/build.gradle
android {

    ・・・

    androidExtensions {
        experimental = true
    }
}
import android.os.Parcelable
import kotlinx.android.parcel.Parcelize

@Parcelize
data class ParcelableDataKotlin(
        var id: Int? = null,
        var name: String? = null
): Parcelable

これが使えるのが一番楽ですね。
これならSerializableではなくParcelable一択でも良い気がします。
ちなみにサポートされている型は以下を参照

注意点としては以下の点がありそうです。
参考:Parcelizeを利用してみた所感と注意点

  • experimentalな機能
    • 正式なリリースがいつなのか。。。
  • Lintのエラーが出る
    • @SuppressLint("ParcelCreator")を付与
  • View Bindingも有効になってしまう
    • app/build.gradleの設定を以下のようにする
app/build.gradle
androidExtensions {
  experimental = true
  features = ["parcelize"]
}

もし、そのほかにも問題がありそうでしたらコメントいただけると幸いです。

PaperPacel

JavaだけでなくKotlinにも対応したParcelable実装のサポートライブラリ
GoogleのAutoValueやTypeAdapterなどにも対応していて、Parcelizeより高機能な感じですね。
※READMEにも記載されていますが、そこまで高機能を求めていないかつexperimentalが気にならないようでしたらParcelizeを選択した方が良いかもしれません

セットアップはGitHubの通りなので省略して、具体的な実装方法は以下の通りです。
2018/09/28現在は、GitHubのWikiページは情報が若干古いのでご注意ください。

ドキュメントは以下のページを参照した方が良さそうです。

@PaperParcel 
data class ParcelableDataKotlin(
        var id: Int? = null,
        var name: String? = null
): PaperParcelable {
  companion object {
    @JvmField val CREATOR = PaperParcelParcelableDataKotlin.CREATOR
  }
}

PaperParcel${CLASS_NAME}が自動生成されるクラスで上記例だとPaperParcelParcelableDataKotlinがビルド時に自動生成されます。
こちらはAndroid StudioのLive Templateを利用すると効率的に実装できます。
設定方法はこちらを参照してください。

Parceler

こちらはJava用のライブラリで、Kotlinで使う場合は注意が必要です。
すでにJavaで導入済みなどの理由があれば使うのはありかもしれません。
こちらもAutoValueやRealmなどにも対応しているので、かなり高機能ですね。
使い方は割愛しますがこちらの記事が参考になります。

まとめ

ライブラリに関しては、Parcelizeがexperimentalですが、使うなら

  • シンプルに使うならParcelize
  • もうちょと高機能なものが必要ならPaperParcel
  • Realmなどとの連携や、JavaとKotlinが共存でもともとParcelerを利用などの状況であればParceler

といったところでしょうか。

最初はKotlinでのParcelableの実装方法を紹介していたはずが、気づいたらライブラリの紹介に。。。
やはり、実装するときは楽したいですからね。

けど、きちんと理解するのは大事なことなので、実装方法のまとめも何かの参考になれば幸いです。

34
23
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
34
23