xinthink / auto-data-class

Generator for Kotlin data classes with Gson/Parcelable type adapter
Apache License 2.0
15 stars 1 forks source link
android annotation-processing data-class kapt kotlin

Build Status Download

auto-data-class

An annotation processor generates Kotlin Data Classes and the boilerplates for Parcelable & GSON TypeAdapter. Inspired by AutoValue and its popular extensions auto-value-parcel & auto-value-gson.

Usage

Declare your data model as an interface/abstract class, and annotate it with @DataClass

@DataClass interface Address : Parcelable {
    val street: String?
    val city: String
}

Since version 0.6.0, you can make things even simpler with @Parcelize. You can now define data classes directly, to make the most out of Data Class, and leave the Parcelable stuff to the Kotlin compiler. The @DataClass processor will just generate a Gson TypeAdapter for such a class.

@Parcelize @DataClass data class Address(
    val street: String?,
    val city: String
) : Parcelable

Now build the project, a data class will be generated, with all the boilerplates needed to implement Parcelable & Gson TypeAdapter.

internal data class DC_Address(
    override val street: String?,
    override val city: String
) : Address {

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

    class GsonTypeAdapter(gson: Gson) : TypeAdapter<Address>()
    ...
    companion object {
        val CREATOR: Parcelable.Creator<DC_Address>
        ...
    }
}

Just like how you'll use AutoValue, it's convenient to write factory methods or derived properties to access the generated code.

@DataClass interface Address {
...
    /** derived properties */
    val fullAddress: String
        get() = if (street != null) "$street, $city" else city

    companion object {
        /** factory method */
        fun create(street: String?, city: String): Address = DC_Address(street, city)

        /** Gson TypeAdapter factory method */
        fun typeAdapter(gson: Gson): TypeAdapter<Address> =
            DC_Address.GsonTypeAdapter(gson)
                .apply {
                    // if needed, you can set default values for the omission of the json fields
                    defaultCity = "Beijing"
                    defaultStreet = "Unknown"
                }
    }
}

Furthermore, you can customize the generated code with the @DataProp annotation.

@DataClass interface Address {
    @get:DataProp("street",
        jsonFieldAlternate = arrayOf("street1", "street2"),
        defaultValueLiteral = """"string literal""""
    )
    val street: String?
    ...
}

A TypeAdapterFactory can also be generated, which can be used to setup the Gson instance. All you have to do is annotating an object/interface/class with @GsonTypeAdapterFactory.

// Using objects, you can also use interfaces or abstract classes.
@GsonTypeAdapterFactory object MyTypeAdapterFactory {
    fun create(): TypeAdapterFactory = DC_MyTypeAdapterFactory()
}

So that you can build a Gson instance like this:

GsonBuilder()
    .registerTypeAdapterFactory(MyTypeAdapterFactory.create())
    .create()

See the test cases for more details.

Integration

Using the kotlin-kapt plugin

kapt 'com.fivemiles.auto:auto-data-class-processor:0.7.0'
compile 'com.fivemiles.auto:auto-data-class-lib:0.7.0'

# for testing, optional
kaptTest 'com.fivemiles.auto:auto-data-class-processor:0.7.0'
kaptAndroidTest 'com.fivemiles.auto:auto-data-class-processor:0.7.0'

Developing auto-data-class

If you forked this repo and made some changes, you can test them locally before subimit a PR, by Publishing to Maven Local:

./gradlew publishToMavenLocal

Limitations

The lib is still at its early stage, there's some limitations you should know.

Unsupported Data Types

The following data types are not supported:

Not all parcelable data types are supported, for example, android.util.SparseArray, android.os.Bundle has no built-in support for now, please use ParcelAdapter if these unsupported types are mandatory. You can also shoot me a PR, of cause. :beer:

See this test case for more details.

Overriding Built-in Methods

If you're using interfaces or abstract classes, because of the nature of Kotlin interface and data class, it will be a little difficult to override built-in methods such as toString, hashCode.

There's several ways to handle such a situation.

First, avoid using Parcelable if it's not required or resort to @Parcelize, so that you can define data classes directly, in which method overriding will not be a problem.

Otherwise, prefer extension functions whenever possible.

@DataClass interface Address {
...
    companion object {
        fun Address.alternateToString(): String = ...
    }
}

Or you will have to create a wrapper class which overrides the built-in methods, and delegates all the others to the generated data class.

/** The data class definition, internal usage only. */
@DataClass internal interface PersonInternal {
    val name: String
    val age: Int
}

/** The public wrapper of the data class, in which you can rewrite the built-in methods. */
class Person
private constructor(p: PersonInternal) : PersonInternal by p {

    override fun toString(): String {
        return "My name is $name, I'm $age years old."
    }

    companion object {
        fun create(name: String, age: Int): Person = Person(DC_PersonInternal(name, age))
    }
}

See this test case for more details.

License

Copyright 2017 yingxinwu.g@gmail.com.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

   http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.