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.
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.
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'
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
The lib is still at its early stage, there's some limitations you should know.
The following data types are not supported:
kotlin.Array
, please consider using List
or Set
insteadList<String?>
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.
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.
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.