171

I'm currently using the excellent AutoParcel in my Java project, which facilitates the creation of Parcelable classes.

Now, Kotlin, which I consider for my next project, has this concept of data classes, that automatically generate the equals, hashCode and toString methods.

Is there a convenient way to make a Kotlin data class Parcelable in a convenient way (without implementing the methods manually)?

thalesmello
  • 3,301
  • 3
  • 20
  • 20
  • 3
    Did you tried kapt? http://blog.jetbrains.com/kotlin/2015/06/better-annotation-processing-supporting-stubs-in-kapt/ – Sergey Mashkov Nov 06 '15 at 10:01
  • You mean to use AutoParcel with Kotlin? I thought about that, but if there was a way to have data classes Parcelable built in into the language, it would be beautiful. Specially considering a huge part of Kotlin usage will come from Android applications. – thalesmello Nov 06 '15 at 21:39

16 Answers16

267

Kotlin 1.1.4 is out

Android Extensions plugin now includes an automatic Parcelable implementation generator. Declare the serialized properties in a primary constructor and add a @Parcelize annotation, and writeToParcel()/createFromParcel() methods will be created automatically:

@Parcelize
class User(val firstName: String, val lastName: String) : Parcelable

So you need to enable them adding this to you module's build.gradle:

apply plugin: 'org.jetbrains.kotlin.android.extensions'

android {
    androidExtensions {
        experimental = true
    }
}
Johnny Five
  • 987
  • 1
  • 14
  • 29
Dhaval Jivani
  • 9,467
  • 2
  • 48
  • 42
  • 2
    For those who want to check it out: https://blog.jetbrains.com/kotlin/2017/08/kotlin-1-1-4-is-out/ – thalesmello Aug 15 '17 at 19:21
  • 4
    why is this no longer data class. Is this example just to show that this could be applied on any generic kotlin class? – Nitin Jain Aug 23 '17 at 05:15
  • @NitinJain, if you read the link posted by @thalesmello, you'll see that this works on `data` and non-`data` classes. – AutonomousApps Sep 04 '17 at 07:50
  • 11
    compiler complains `this calss implements Parcelable but does not provice CREATOR field`. Is your answer enough (full)? – murt Sep 15 '17 at 14:20
  • **Edited:** Ok compailer complains, but project Rebuild successfully, and after all it works. – murt Sep 15 '17 at 14:43
  • 1
    @murt Did you successfully use Parcelable? I'm facing the compile error because of CREATOR – TOP Sep 19 '17 at 02:10
  • 1
    Yes, do everything in this answer and it will work. buildscript { ext.kotlin_version = '1.2-M2' ... repositories {... maven { url "https://dl.bintray.com/kotlin/kotlin-dev/"}} dependencies {... classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" }} – murt Sep 19 '17 at 07:16
  • 4
    You can use `@SuppressLint("ParcelCreator")` to get rid of the lint warning. – Dutch Masters Sep 30 '17 at 03:13
  • I got a weird crash when using this feature, after leaving the activity that has it in one of its views: java.lang.NoSuchMethodError: No virtual method writeSerializable(Ljava/util/Date;)V in class Landroid/os/Parcel; or its super classes (declaration of 'android.os.Parcel' appears in /system/framework/framework.jar) . Seems it is still not stable : https://stackoverflow.com/q/46592138/878126 – android developer Nov 01 '17 at 15:53
  • Be careful with @Parcelable, it may cause INSTALL_FAILED_UID_CHANGED errors https://discuss.kotlinlang.org/t/parcelize-causes-install-failed-dexopt-on-android-4/4662/2 – IvanP Nov 09 '17 at 15:40
  • Does this work fine now, with Kotlin version 1.2.10 ? – android developer Jan 17 '18 at 09:10
  • 1
    Don't Forget @Parcelize annotation on the top of class – Moises Portillo Jun 29 '18 at 14:20
  • How to pass array list via Intent putParcelableArrayListExtra ?? – Najib.Nj Mar 07 '19 at 17:38
  • ...be forewarned, `These features are not considered production ready yet`; https://kotlinlang.org/docs/tutorials/android-plugin.html – Tom Howard May 15 '19 at 20:09
51

You can try this plugin:

android-parcelable-intellij-plugin-kotlin

It help you generate Android Parcelable boilerplate code for kotlin's data class. And it finally look like this:

data class Model(var test1: Int, var test2: Int): Parcelable {

    constructor(source: Parcel): this(source.readInt(), source.readInt())

    override fun describeContents(): Int {
        return 0
    }

    override fun writeToParcel(dest: Parcel?, flags: Int) {
        dest?.writeInt(this.test1)
        dest?.writeInt(this.test2)
    }

    companion object {
        @JvmField final val CREATOR: Parcelable.Creator<Model> = object : Parcelable.Creator<Model> {
            override fun createFromParcel(source: Parcel): Model{
                return Model(source)
            }

            override fun newArray(size: Int): Array<Model?> {
                return arrayOfNulls(size)
            }
        }
    }
}
nekocode
  • 519
  • 1
  • 3
  • 4
36

Just click on the data keyword of your kotlin data class, then press alt+Enter, select the first option saying "Add Parceable Implementation"

Manish Patiyal
  • 4,427
  • 5
  • 21
  • 35
  • 5
    I used this method, but it has several problems. Sometimes it substitutes a `parcel.read...` with `TODO`, if a field is not `val/var`. If you use `List` inside a class, your implementation becomes a problem. So I turned to `@Parcelize` in the accepted answer. – CoolMind Jan 23 '19 at 10:52
25

A simpler and up-to-date method would be to use the @Parcelize annotation which removes the hard word work of implementing the Parcelable

build.gradle (app module)

apply plugin: "kotlin-parcelize"

// If you are using the new plugin format use this instead.
plugins{
    ...
    id "kotlin-parcelize"
}

YourClass.kt

import kotlinx.parcelize

@Parcelize
class User(val firstName: String, val lastName: String, val age: Int): Parcelable
Andrew Chelix
  • 1,012
  • 11
  • 16
19
  • Use @Parcelize annotation on top of your Model / Data class
  • Use latest version of Kotlin
  • Use latest version of Kotlin Android Extensions in your app module

Example :

@Parcelize
data class Item(
    var imageUrl: String,
    var title: String,
    var description: Category
) : Parcelable
Manoj Bhadane
  • 607
  • 5
  • 11
18

Have you tried PaperParcel? It's an annotation processor which automatically generates the Android Parcelable boilerplate code for you.

Usage:

Annotate your data class with @PaperParcel, implement PaperParcelable, and add a JVM static instance of the generated CREATOR e.g.:

@PaperParcel
data class Example(
  val test: Int,
  ...
) : PaperParcelable {
  companion object {
    @JvmField val CREATOR = PaperParcelExample.CREATOR
  }
}

Now your data class is Parcelable and can be passed directly to a Bundle or Intent

Edit: Update with latest API

Bradley Campbell
  • 9,298
  • 6
  • 37
  • 47
  • The IDE now says "Data class inheritance from other classes is forbidden". I think PaperParcel cannot be used anymore with data classes... – Massimo Feb 01 '17 at 11:54
  • They never could inherit from other classes, but they can implement interfaces (e.g. PaperParcelable) – Bradley Campbell Feb 02 '17 at 13:04
  • 1
    @Bradley Campbell This gives me an error in this line JvmField val CREATOR = PaperParcelExample.CREATOR cannot create PaperParcelExample class – Mr.G Aug 01 '17 at 13:27
17

The best way with no boilerplate code at all is Smuggler gradle plugin. All you need is just implement AutoParcelable interface like

data class Person(val name:String, val age:Int): AutoParcelable

And that's all. Works for sealed classes as well. Also this plugin provides compile time validation for all AutoParcelable classes.

UPD 17.08.2017 Now with Kotlin 1.1.4 and Kotlin Android extensions plugin you could use @Parcelize annotation. In this case, the example above will look like:

@Parcelize class Person(val name:String, val age:Int): Parcelable

No need for data modifier. The biggest downside, for now, is using kotlin-android-extensions plugin which has a lot of other functions that could be unnecessary.

Stepango
  • 4,721
  • 3
  • 31
  • 44
6

Using Android Studio and the Kotlin plugin, I found an easy way to convert my old Java Parcelables with no extra plugins (if all you want is to turn a brand new data class into a Parcelable, skip to the 4th code snippet).

Let's say you have a Person class with all the Parcelable boiler plate:

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

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

    private final String firstName;
    private final String lastName;
    private final int age;

    public Person(String firstName, String lastName, int age) {
        this.firstName = firstName;
        this.lastName = lastName;
        this.age = age;
    }

    protected Person(Parcel in) {
        firstName = in.readString();
        lastName = in.readString();
        age = in.readInt();
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeString(firstName);
        dest.writeString(lastName);
        dest.writeInt(age);
    }

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

    public String getFirstName() {
        return firstName;
    }

    public String getLastName() {
        return lastName;
    }

    public int getAge() {
        return age;
    }
}

Start by stripping out the Parcelable implementation, leaving a bare-bones, plain, old Java object (properties should be final and set by the constructor):

public class Person {
    private final String firstName;
    private final String lastName;
    private final int age;

    public Person(String firstName, String lastName, int age) {
        this.firstName = firstName;
        this.lastName = lastName;
        this.age = age;
    }

    public String getFirstName() {
        return firstName;
    }

    public String getLastName() {
        return lastName;
    }

    public int getAge() {
        return age;
    }
}

Then let the Code > Convert Java file to Kotlin File option do its magic:

class Person(val firstName: String, val lastName: String, val age: Int)

Convert this into a data class:

data class Person(val firstName: String, val lastName: String, val age: Int)

And finally, let's turn this into a Parcelable again. Hover the class name and Android Studio should give you the option to Add Parcelable Implementation. The result should look like this:

data class Person(val firstName: String, val lastName: String, val age: Int) : Parcelable {
    constructor(parcel: Parcel) : this(
            parcel.readString(),
            parcel.readString(),
            parcel.readInt()
    )

    override fun writeToParcel(parcel: Parcel, flags: Int) {
        parcel.writeString(firstName)
        parcel.writeString(lastName)
        parcel.writeInt(age)
    }

    override fun describeContents(): Int {
        return 0
    }

    companion object CREATOR : Parcelable.Creator<Person> {
        override fun createFromParcel(parcel: Parcel): Person {
            return Person(parcel)
        }

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

As you can see, the Parcelable implementation is some auto-generated code appended to you data class definition.

Notes:

  1. Trying to convert a Java Parcelable directly into Kotlin will not produce the same result with the current version of the Kotlin plugin (1.1.3).
  2. I had to remove some extra curly braces the current Parcelable code generator introduces. Must be a minor bug.

I hope this tip works for you as well as it did for me.

argenkiwi
  • 2,134
  • 1
  • 23
  • 26
5

I will leave my way of doing in case it might help someone.

What i do is i have a generic Parcelable

interface DefaultParcelable : Parcelable {
    override fun describeContents(): Int = 0

    companion object {
        fun <T> generateCreator(create: (source: Parcel) -> T): Parcelable.Creator<T> = object: Parcelable.Creator<T> {
            override fun createFromParcel(source: Parcel): T = create(source)

            override fun newArray(size: Int): Array<out T>? = newArray(size)
        }

    }
}

inline fun <reified T> Parcel.read(): T = readValue(T::class.javaClass.classLoader) as T
fun Parcel.write(vararg values: Any?) = values.forEach { writeValue(it) }

And then i create parcelables like this:

data class MyParcelable(val data1: Data1, val data2: Data2) : DefaultParcelable {
    override fun writeToParcel(dest: Parcel, flags: Int) { dest.write(data1, data2) }
    companion object { @JvmField final val CREATOR = DefaultParcelable.generateCreator { MyParcelable(it.read(), it.read()) } }
}

Which gets me rid of that boilerplate override.

gmemario
  • 1,190
  • 23
  • 35
5

You can do it using @Parcelize annotation. It is an automatic Parcelable implementation generator.

First, you need to enable them adding this to your module's build.gradle:

apply plugin: 'org.jetbrains.kotlin.android.extensions'

Declare the serialized properties in a primary constructor and add a @Parcelize annotation, and writeToParcel()/createFromParcel() methods will be created automatically:

@Parcelize
class User(val firstName: String, val lastName: String) : Parcelable

You DONT need to add experimental = true inside androidExtensions block.

Malwinder Singh
  • 6,644
  • 14
  • 65
  • 103
3

Unfortunately there is no way in Kotlin to put a real field in an interface, so you can't inherit it from an interface-adapter for free: data class Par : MyParcelable

You may look at delegation, but it does not help with fields, AFAIK: https://kotlinlang.org/docs/reference/delegation.html

So the the only option I see is a fabric function for Parcelable.Creator, which is kind of obvious.

voddan
  • 31,956
  • 8
  • 77
  • 87
3

Below Kotlin code can help you to create Parcelable data classes with Parent and Child Data class

Parent Data Class - MyPostModel

data class MyPostModel(
    @SerializedName("post_id") val post_id: String? = "",
    @SerializedName("category_id") val category_id: String? = "",
    @SerializedName("images") val images: ArrayList<ImagesModel>? = null
) : Parcelable {
    constructor(parcel: Parcel) : this(
        parcel.readString(),
        parcel.readString(),
        parcel.createTypedArrayList(ImagesModel.CREATOR)
    )

    override fun writeToParcel(parcel: Parcel, flags: Int) {
        parcel.writeString(post_id)
        parcel.writeString(category_id)
        parcel.writeTypedList(images)
    }

    override fun describeContents(): Int {
        return 0
    }

    companion object CREATOR : Parcelable.Creator<MyPostModel> {
        override fun createFromParcel(parcel: Parcel): MyPostModel {
            return MyPostModel(parcel)
        }

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

Child Data Class - ImagesModel

data class ImagesModel(
    @SerializedName("image_id") val image_id: String? = "",
    @SerializedName("image_url") val image_url: String? = ""
) : Parcelable {
    constructor(parcel: Parcel) : this(
        parcel.readString(),
        parcel.readString()
    )

    override fun writeToParcel(parcel: Parcel, flags: Int) {
        parcel.writeString(image_id)
        parcel.writeString(image_url)
    }

    override fun describeContents(): Int {
        return 0
    }

    companion object CREATOR : Parcelable.Creator<ImagesModel> {
        override fun createFromParcel(parcel: Parcel): ImagesModel {
            return ImagesModel(parcel)
        }

        override fun newArray(size: Int): Array<ImagesModel?> {
            return arrayOfNulls(size)
        }
    }
}
Pratik Dodiya
  • 2,337
  • 1
  • 19
  • 12
2

i prefer just using the https://github.com/johncarl81/parceler lib with

@Parcel(Parcel.Serialization.BEAN)
data class MyClass(val value)
Jan Rabe
  • 398
  • 2
  • 12
  • This gives the error "Class 'MyClass' is not abstract and does not implement abstract member public abstract fun writeToParcel(dest: Parcel!, flags: Int): Unit defined in android.os.Parcelable – PhillyTheThrilly May 12 '20 at 17:59
1

Kotlin has made the entire process of Parcelization in Android damn easy with its @Parcel annotation.

To do that

Step 1. Add Kotlin extensions in your app module gradle

Step 2. Add experimental = true since this feature is still in experimentation in gradle.

androidExtensions { experimental = true }

Step 3. Annonate the data class with @Parcel

Here is an simple example on @Parcel usage

Ramakrishna Joshi
  • 1,442
  • 17
  • 22
0

There is a Plugin but is not always as updated as Kotlin is evolving: https://plugins.jetbrains.com/plugin/8086

Alternative: I have a working example of a custom data class using Parcelable and lists:

Data classes using Parcelable with Lists:

https://gist.github.com/juanchosaravia/0b61204551d4ec815bbf

Hope it helps!

Juan Saravia
  • 7,661
  • 6
  • 29
  • 41
0

Since nobody has mentioned this point ( Or rather, not provided specific detail on versions), I am going comment real quick on this:

I tried all the possible combinations of @Parcelize using the above guidance, but was not successful in getting it to work.

If your Android studio shows error on your class that describeContents or writeToParcel is not implemented, even with the @Parcelize annotation, please check that in your app/build.gradle you don't have apply plugin: 'kotlin-android-extensions' enabled.

This is not working. For some reason, my project was using older deprecated version of extensions (ktxVersion = '1.0.2'). So this was conflicting with the @Parcelize implementation.

When I tried to add apply plugin: 'kotlin-parcelize', it said it's not going to work with the extensions simultaneously and I can have only one or the other.

After working with a colleague, we figured out that the extension was causing the whole @Parcelize to fail. I tried so many different things, but even after getting over the compile time errors, runtime error would say "CREATOR" not found exception or similar.

So finally I removed the apply plugin for kotlin-android-extensions and just included the apply plugin: 'kotlin-parcelize', which fixed that issue, and it works as expected.

k007sam
  • 36
  • 3