UPDATE: M11 of Kotlin has added multiple constructors, making this post mostly irrelevant

I've recently been trying to learn Kotlin, specifically Android development in Kotlin. In fact, I'm speaking in public, in front of other humans, where I might say something dumb and ruin my reputation, about this very topic at Droidcon Montreal in April.

So I've been up late the past few weeks trying to prepare, and tweeting more about Kotlin than I normally do. Someone even took notice and asked me a question, thanks @Adel!

My first thought was, "quick, look like you already know"

As if I could pluck this from a repo somewhere once I get back to my computer. Truthfully, I'm not a big user of Parcelable. I generally prefer Gson serialization and passing the String as an Extra in the Bundle. Of course there are times where Parcelable is the way to go, and Gson serialization isn't free.

If you're unfamiliar with Kotlin, you might wonder why there is a question here at all. There are language features in Kotlin such as not having fields on classes; also, properties on classes have backing fields, so these might make you wonder if something in Kotlin might work different for Parcelable. Though, the main issue is that Kotlin does not support multiple constructors, and the general pattern for implementing Parcelable on a class includes a constructor which takes a Parcel.

Parcelable Review

Let's review implementing Parcelable in Java (reference). In order to make a class Parcelable you have to add four things to your class:

  • Implement describeContents() which is used to describe the Parcelable's marshalled representation, but I always just return 0
  • Implement writeToParcel(), which I generally just use the Parcel methods to write the data of my object to the Parcel, i.e., destParcel.writeInt(age) or destParcel.writeTypedList(userList)
  • A static field called CREATOR, which is an object implementing the Parcelable.Creator<T>, which is used to either create your object of type class from a Parcel.
  • A constructor for your class that accepts a Parcel and can set your properties and fields to that Parcel, i.e., Parcel with a method body with lines like this.age = in.readInt(). Note: the calls on Parcel must be read in the same order they were written.

Example

I'm currently writing an app for the Christian ministry Desiring God. It's a daily devotional app called Solid Joys disclaimer: I did NOT write version one. I also have full premission to share this code.

The domain model for the app is simple. There is a Devotional class that contains ScriptureReference's. You can retreive a list of Devotional's that will be written to a DevotionalResponse object.

To start my example I have written my domain in Java classes and have written a test Activity in Kotlin that creates a mock DevotionalResponse, writes the it to a Bundle with bundle.putParcelable(), and then checks that what is read out is the same to what was written in. Here is the code:

TestActivity.kt
public class TestActivity : Activity() {  
    val DEVOTIONAL_RESPONSE: String = "DEVOTIONAL_RESPONSE"
    var holdr: Holdr_ActivityTest? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_test)
        holdr = Holdr_ActivityTest(findViewById(android.R.id.content))

        //create response
        var response: DevotionalResponse = DevotionalResponse()
        fillInResponse(response)

        //write it to a Bundle
        var newBundle: Bundle = Bundle()
        newBundle.putParcelable(DEVOTIONAL_RESPONSE, response)

        //read it from the new Bundle
        var newResponse: DevotionalResponse = newBundle.getParcelable(DEVOTIONAL_RESPONSE)

        //check that they're the same
        assert(newResponse.getDevotionals().get(0).getContent().equalsIgnoreCase(response.getDevotionals().get(0).getContent()))
    }
}
DevotionalResponse.java
public class DevotionalResponse implements Parcelable {

    public DevotionalResponse() {
        //default constructor
    }

    @Expose
    private List<Devotional> devotionals = new ArrayList<>();

    /**
     *
     * @return
     * The devotionals
     */
    public List<Devotional> getDevotionals() {
        return devotionals;
    }

    /**
     *
     * @param devotionals
     * The devotionals
     */
    public void setDevotionals(List<Devotional> devotionals) {
        this.devotionals = devotionals;
    }

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

    @Override
    public void writeToParcel(Parcel dest, int flags) {

        dest.writeTypedList(devotionals);
    }

    public static final Parcelable.Creator<DevotionalResponse> CREATOR = new Parcelable.Creator<DevotionalResponse>() {
        public DevotionalResponse createFromParcel(Parcel in) {
            return new DevotionalResponse(in);
        }

        public DevotionalResponse[] newArray(int size) {
            return new DevotionalResponse[size];
        }
    };

    public DevotionalResponse(Parcel in){

        in.readTypedList(this.devotionals, Devotional.CREATOR);
    }
}
Devotional.java
public class Devotional implements Parcelable {

    public Devotional() {
        //default constructor
    }

    @Expose
    private Integer month;
    @Expose
    private Integer day;
    @Expose
    private String title;
    @Expose
    private String content;
    @SerializedName("reference_text")
    @Expose
    private String referenceText;
    @SerializedName("reference_url")
    @Expose
    private String referenceUrl;
    @SerializedName("canonical_url")
    @Expose
    private String canonicalUrl;
    @SerializedName("short_url")
    @Expose
    private String shortUrl;
    @Expose
    private String sjuid;
    @SerializedName("scripture_references")
    @Expose
    private List<ScriptureReference> scriptureReferences = new ArrayList<>();

    /**
     *
     * @return
     * The month
     */
    public Integer getMonth() {
        return month;
    }

    /**
     *
     * @param month
     * The month
     */
    public void setMonth(Integer month) {
        this.month = month;
    }

    /**
     *
     * @return
     * The day
     */
    public Integer getDay() {
        return day;
    }

    /**
     *
     * @param day
     * The day
     */
    public void setDay(Integer day) {
        this.day = day;
    }

    /**
     *
     * @return
     * The title
     */
    public String getTitle() {
        return title;
    }

    /**
     *
     * @param title
     * The title
     */
    public void setTitle(String title) {
        this.title = title;
    }

    /**
     *
     * @return
     * The content
     */
    public String getContent() {
        return content;
    }

    /**
     *
     * @param content
     * The content
     */
    public void setContent(String content) {
        this.content = content;
    }

    /**
     *
     * @return
     * The referenceText
     */
    public String getReferenceText() {
        return referenceText;
    }

    /**
     *
     * @param referenceText
     * The reference_text
     */
    public void setReferenceText(String referenceText) {
        this.referenceText = referenceText;
    }

    /**
     *
     * @return
     * The referenceUrl
     */
    public String getReferenceUrl() {
        return referenceUrl;
    }

    /**
     *
     * @param referenceUrl
     * The reference_url
     */
    public void setReferenceUrl(String referenceUrl) {
        this.referenceUrl = referenceUrl;
    }

    /**
     *
     * @return
     * The canonicalUrl
     */
    public String getCanonicalUrl() {
        return canonicalUrl;
    }

    /**
     *
     * @param canonicalUrl
     * The canonical_url
     */
    public void setCanonicalUrl(String canonicalUrl) {
        this.canonicalUrl = canonicalUrl;
    }

    /**
     *
     * @return
     * The shortUrl
     */
    public String getShortUrl() {
        return shortUrl;
    }

    /**
     *
     * @param shortUrl
     * The short_url
     */
    public void setShortUrl(String shortUrl) {
        this.shortUrl = shortUrl;
    }

    /**
     *
     * @return
     * The sjuid
     */
    public String getSjuid() {
        return sjuid;
    }

    /**
     *
     * @param sjuid
     * The sjuid
     */
    public void setSjuid(String sjuid) {
        this.sjuid = sjuid;
    }

    /**
     *
     * @return
     * The scriptureReferences
     */
    public List<ScriptureReference> getScriptureReferences() {
        return scriptureReferences;
    }

    /**
     *
     * @param scriptureReferences
     * The scripture_references
     */
    public void setScriptureReferences(List<ScriptureReference> scriptureReferences) {
        this.scriptureReferences = scriptureReferences;
    }

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

    @Override
    public void writeToParcel(Parcel dest, int flags) {

        dest.writeInt(month);
        dest.writeInt(day);
        dest.writeString(title);
        dest.writeString(content);
        dest.writeString(referenceText);
        dest.writeString(referenceUrl);
        dest.writeString(canonicalUrl);
        dest.writeString(shortUrl);
        dest.writeString(sjuid);
        dest.writeTypedList(scriptureReferences);
    }

    public static final Parcelable.Creator<Devotional> CREATOR = new Parcelable.Creator<Devotional>() {
        public Devotional createFromParcel(Parcel in) {
            return new Devotional(in);
        }

        public Devotional[] newArray(int size) {
            return new Devotional[size];
        }
    };

    private Devotional(Parcel in){

        this.month = in.readInt();
        this.day = in.readInt();
        this.title = in.readString();
        this.content = in.readString();
        this.referenceText = in.readString();
        this.referenceUrl = in.readString();
        this.canonicalUrl = in.readString();
        this.shortUrl = in.readString();
        this.sjuid = in.readString();
        in.readTypedList(this.scriptureReferences, ScriptureReference.CREATOR);
    }
}
ScriptureReference.java
public class ScriptureReference implements Parcelable {

    public ScriptureReference() {
        //default constructor
    }

    @SerializedName("book_name")
    @Expose
    private String bookName;
    @Expose
    private Integer book;
    @SerializedName("start_chapter")
    @Expose
    private Integer startChapter;
    @SerializedName("start_verse")
    @Expose
    private Integer startVerse;
    @SerializedName("end_chapter")
    @Expose
    private Integer endChapter;
    @SerializedName("end_verse")
    @Expose
    private Integer endVerse;

    /**
     *
     * @return
     * The bookName
     */
    public String getBookName() {
        return bookName;
    }

    /**
     *
     * @param bookName
     * The book_name
     */
    public void setBookName(String bookName) {
        this.bookName = bookName;
    }

    /**
     *
     * @return
     * The book
     */
    public Integer getBook() {
        return book;
    }

    /**
     *
     * @param book
     * The book
     */
    public void setBook(Integer book) {
        this.book = book;
    }

    /**
     *
     * @return
     * The startChapter
     */
    public Integer getStartChapter() {
        return startChapter;
    }

    /**
     *
     * @param startChapter
     * The start_chapter
     */
    public void setStartChapter(Integer startChapter) {
        this.startChapter = startChapter;
    }

    /**
     *
     * @return
     * The startVerse
     */
    public Integer getStartVerse() {
        return startVerse;
    }

    /**
     *
     * @param startVerse
     * The start_verse
     */
    public void setStartVerse(Integer startVerse) {
        this.startVerse = startVerse;
    }

    /**
     *
     * @return
     * The endChapter
     */
    public Integer getEndChapter() {
        return endChapter;
    }

    /**
     *
     * @param endChapter
     * The end_chapter
     */
    public void setEndChapter(Integer endChapter) {
        this.endChapter = endChapter;
    }

    /**
     *
     * @return
     * The endVerse
     */
    public Integer getEndVerse() {
        return endVerse;
    }

    /**
     *
     * @param endVerse
     * The end_verse
     */
    public void setEndVerse(Integer endVerse) {
        this.endVerse = endVerse;
    }

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

    @Override
    public void writeToParcel(Parcel dest, int flags) {

        dest.writeString(bookName);
        dest.writeInt(book);
        dest.writeInt(startChapter);
        dest.writeInt(startVerse);
        dest.writeInt(endChapter);
        dest.writeInt(endVerse);

    }

    public static final Parcelable.Creator<ScriptureReference> CREATOR = new Parcelable.Creator<ScriptureReference>() {
        public ScriptureReference createFromParcel(Parcel in) {
            return new ScriptureReference(in);
        }

        public ScriptureReference[] newArray(int size) {
            return new ScriptureReference[size];
        }
    };

    public ScriptureReference(Parcel in){

        this.bookName = in.readString();
        this.book = in.readInt();
        this.startChapter = in.readInt();
        this.startVerse = in.readInt();
        this.endChapter = in.readInt();
        this.endVerse = in.readInt();

    }
}

SIDE NOTE: you can <⌥⇧⌘ J on a Mac or <Shift+Ctrl+Alt+J> on Windows to convert any Java file to Kotlin in IntelliJ or Android Studio. Results may vary.

Converted to Kotlin

So far our example is pretty straight forward. It's worthy of mention that we currently have 510 lines of Java in our domain model. Once we convert to Kotlin our domain model shrinks to a mere 201 lines of Kotlin (would be even smaller if I didn't need Gson annotations), but I did slightly cheat by removing the comments. Still, as you can see below, much less verbose.

After converting our domain to Kotlin we have:

DevotionalResponse.kt
public data class DevotionalResponse : Parcelable {

    public var devotionals: MutableList<Devotional> = ArrayList()

    override fun describeContents(): Int {

        return 0
    }

    override fun writeToParcel(dest: Parcel, flags: Int) {

        dest.writeTypedList<Devotional>(devotionals)
    }

    class object {

        public val CREATOR: Parcelable.Creator<DevotionalResponse> = object : Parcelable.Creator<DevotionalResponse> {
            override fun createFromParcel(parcelIn: Parcel): DevotionalResponse {
                return DevotionalResponse(parcelIn)
            }

            override fun newArray(size: Int): Array<DevotionalResponse> {
                return Array(size, {i -> DevotionalResponse()})
            }
        }
    }

}

private fun DevotionalResponse(parcelIn:Parcel):DevotionalResponse {

    val devotionalResponse = DevotionalResponse()

    parcelIn.readTypedList<Devotional>(devotionalResponse.devotionals, Devotional.CREATOR)

    return devotionalResponse
}
Devotional.kt
public data class Devotional : Parcelable {

    public var month: Int? = null
    public var day: Int? = null
    public var title: String? = null
    public var content: String? = null
    SerializedName("reference_text")
    public var referenceText: String? = null
    SerializedName("reference_url")
    public var referenceUrl: String? = null
    SerializedName("canonical_url")
    public var canonicalUrl: String? = null
    SerializedName("short_url")
    public var shortUrl: String? = null
    public var sjuid: String? = null
    SerializedName("scripture_references")
    public var scriptureReferences: MutableList<ScriptureReference> = ArrayList()

    override fun describeContents(): Int {
        return 0
    }

    override fun writeToParcel(dest:Parcel, flags:Int) {

        dest.writeInt(month!!)
        dest.writeInt(day!!)
        dest.writeString(title)
        dest.writeString(content)
        dest.writeString(referenceText)
        dest.writeString(referenceUrl)
        dest.writeString(canonicalUrl)
        dest.writeString(shortUrl)
        dest.writeString(sjuid)
        dest.writeTypedList<ScriptureReference>(scriptureReferences)
    }

    class object {
        public val CREATOR: Parcelable.Creator<Devotional> = object:Parcelable.Creator<Devotional> {
            override fun createFromParcel(parcelIn:Parcel): Devotional {
                return Devotional(parcelIn)
            }

            override fun newArray(size: Int): Array<Devotional> {
                return Array(size, {i -> Devotional()})
            }
        }
    }
}

private fun Devotional(parcelIn: Parcel):Devotional {  
    val devotional = Devotional()

    devotional.month = parcelIn.readInt()
    devotional.day = parcelIn.readInt()
    devotional.title = parcelIn.readString()
    devotional.content = parcelIn.readString()
    devotional.referenceText = parcelIn.readString()
    devotional.referenceUrl = parcelIn.readString()
    devotional.canonicalUrl = parcelIn.readString()
    devotional.shortUrl = parcelIn.readString()
    devotional.sjuid = parcelIn.readString()
    parcelIn.readTypedList<ScriptureReference>(devotional.scriptureReferences, ScriptureReference.CREATOR)

    return devotional
}
ScriptureReference.kt
public data class ScriptureReference : Parcelable {

    SerializedName("book_name")
    public var bookName: String? = null
    public var book: Int? = null
    SerializedName("start_chapter")
    public var startChapter: Int? = null
    SerializedName("start_verse")
    public var startVerse: Int? = null
    SerializedName("end_chapter")
    public var endChapter: Int? = null
    SerializedName("end_verse")
    public var endVerse: Int? = null

    override fun describeContents(): Int {
        return 0
    }

    override fun writeToParcel(dest: Parcel, flags: Int) {

        dest.writeString(bookName)
        dest.writeInt(book!!)
        dest.writeInt(startChapter!!)
        dest.writeInt(startVerse!!)
        dest.writeInt(endChapter!!)
        dest.writeInt(endVerse!!)

    }

    class object {

        public val CREATOR: Parcelable.Creator<ScriptureReference> = object : Parcelable.Creator<ScriptureReference> {
            override fun createFromParcel(parcelIn: Parcel): ScriptureReference {
                return ScriptureReference(parcelIn)
            }

            override fun newArray(size: Int): Array<ScriptureReference> {
                return Array(size, {i -> ScriptureReference()})
            }
        }
    }
}

private fun ScriptureReference(parcelIn:Parcel):ScriptureReference {  
    val scriptureReference = ScriptureReference()

    scriptureReference.bookName = parcelIn.readString()
    scriptureReference.book = parcelIn.readInt()
    scriptureReference.startChapter = parcelIn.readInt()
    scriptureReference.startVerse = parcelIn.readInt()
    scriptureReference.endChapter = parcelIn.readInt()
    scriptureReference.endVerse = parcelIn.readInt()

    return scriptureReference
}

And our updated TestActivity.kt (updated since we have to do deal with properties instead of setters and getters in Kotlin, not to mention the added null safety provided now by using Kotlin):

TestActivity.kt
public class TestActivity : Activity() {

    val DEVOTIONAL_RESPONSE: String = "DEVOTIONAL_RESPONSE"

    var holdr: Holdr_ActivityTest? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        setContentView(R.layout.activity_test)

        holdr = Holdr_ActivityTest(findViewById(android.R.id.content))

        //create response
        var response: DevotionalResponse = DevotionalResponse()
        fillInResponse(response)

        //write it to a Bundle
        var newBundle: Bundle = Bundle()
        newBundle.putParcelable(DEVOTIONAL_RESPONSE, response)

        //read it from the new Bundle
        var newResponse: DevotionalResponse = newBundle.getParcelable(DEVOTIONAL_RESPONSE)

        //assert to see if a property on the object made it through parceling
            assert(newResponse.devotionals.get(0).scriptureReferences.get(0).bookName?.equalsIgnoreCase(response.devotionals.get(0).scriptureReferences.get(0).bookName?: "") as Boolean)
    }
}

Solution

So, how did we solve not having the ability to create a new constructor?

private fun Devotional(parcelIn: Parcel):Devotional {  
    val devotional = Devotional()

    devotional.month = parcelIn.readInt()
    devotional.day = parcelIn.readInt()
    devotional.title = parcelIn.readString()
    devotional.content = parcelIn.readString()
    devotional.referenceText = parcelIn.readString()
    devotional.referenceUrl = parcelIn.readString()
    devotional.canonicalUrl = parcelIn.readString()
    devotional.shortUrl = parcelIn.readString()
    devotional.sjuid = parcelIn.readString()
    parcelIn.readTypedList<ScriptureReference>(devotional.scriptureReferences, ScriptureReference.CREATOR)

    return devotional
}

We created an extension function with the name of the class that accepted a Parcel, created an instance of our class, set the properties of the new object from the Parcel and returned the new instance.

This is much like a static factory method on a class in Java, but this is not really possible in Java, or rather, it wouldn't look semantically the same as creating a new instance like it does in Kotlin. That is, in Java with a static factory we'd have something like Devotional.Devotional(parcelIn) as well as new Devotional(). In Kotlin though, our extention function looks like a constructor to our class, i.e., Devotional(parcelIn) and Devotional().