Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Multiple ParcelConstructor-annotated methods in Kotlin. #310

Open
jlaunonen opened this issue Aug 15, 2017 · 4 comments
Open

Multiple ParcelConstructor-annotated methods in Kotlin. #310

jlaunonen opened this issue Aug 15, 2017 · 4 comments

Comments

@jlaunonen
Copy link

jlaunonen commented Aug 15, 2017

Somewhat regarding to #275 comment 3 ... This may be more of just a documentation than an issue with Parceler, but here we go...

Kotlin allows parameters to have default values in function and constructor definitions. This (among other things) allows compact definition of data classes (think them as POJO's). e.g.

data class TimeStamp(val value: Long = System.currentTimeMillis())

As Parceler does the handling of serialization and deserialization of view models and whatnot keeping programmers sane in Android-world, consider following (bad) example:

@Parcel(Parcel.Serialization.BEAN)
data class MyScreenViewModel @ParcelConstructor constructor(
    val title: ObservableField<String> = ObservableField(),
    val subTitle: ObservableField<String> = ObservableField()
) {
    // This provided here only as some kind of additional non-default behaviour.
    // Has nothing to do with the issue otherwise and could be left away.
    constructor(title: String) : this(
        title = ObservableField(title)
    )
}

One can now create instances of that class in few ways:

val vmDefault = MyScreenViewModel() // calls the primary ctor with all defaults.
   .apply { title.set("Options") } // syntactic kotlin-sugar.
val vmSpecific = MyScreenViewModel("Options") // calls secondary ctor; not an issue.

However, this doesn't work with Parceler (at least 1.1.8...1.1.9) due error Parceler: Too many @ParcelConstructor annotated constructors found..

Insides

It seems that if all arguments of a data class have default parameters, kotlinc will generate a default no-argument constructor with the @ParcelConstructor annotation and the compilation will fail. If there is at least one argument without default value, compilation seems to succeed even though there are multiple of annotated constructors.

Internal decompilation shows, that there is at least one synthetic method added to data classes that handles the optionality of the parameters case by case (var3 is a bitmask):

data class Test @ParcelConstructor constructor(val i: Int, val s: String? = null)
->
// expected
@ParcelConstructor public Test(int i, @Nullable String s)  
// synthetic, causes no error.
@ParcelConstructor public Test(int var1, String var2,
    int var3, DefaultConstructorMarker var4)

If all arguments have defaults, there will be default constructor too:

data class Test @ParcelConstructor constructor(val i: Int = 4, val s: String? = null)
->
// extra
@ParcelConstructor public Test() : this(4, null, 3, null)
// full
@ParcelConstructor public Test(int i, @Nullable String s)  
// synthetic
@ParcelConstructor public Test(int var1, String var2,
    int var3, DefaultConstructorMarker var4)

The synthetic constructor always calls full/expected constructor.
The default constructor calls synthetic constructor.

Workarounds

1

The issue can be worked around by having no defaults in primary constructor, although being a bit more verbose (but still much shorter than its java-version):

data class MyScreenVm @ParcelConstructor constructor(
    val title: ObservableField<String>,
    val subTitle: ObservableField<String>
) {
   constructor() : this(
      title = ObservableField(),
      subTitle = ObservableField()
   )
}

Here the primary constructor has no defaults, thus no synthetic method for defaults is generated, no default constructor is generated (but the explicit one exists without conflicting annotation), and the annotation is on only one constructor.

2

Other workaround is to declare the default constructor by hand and calling the primary constructor with at least one parameter:

data class MyScreenVm @ParcelConstructor constructor(
    val title: ObservableField<String> = ObservableField(),
    val subTitle: ObservableField<String> = ObservableField()
) {
    constructor() : this(title = ObservableField())
}

The default of title is kind of declared twice now, but it forces this to call the primary (or rather the synthetic) constructor and makes the default no-arg constructor to not have conflicting annotation.

@okarakose
Copy link

Is there any workaround made by parceler library side ?

This problem is very bad to using kotlin with data classes and default parameter values.

@johncarl81
Copy link
Owner

What would you suggest @okarakose?

@monowar1993
Copy link

@johncarl81 please put some example on kotlin.

@johncarl81
Copy link
Owner

@monowar1993, all the documentation (readme, website) is fork-friendly. A pull request with some appropriate Kotlin examples would be much appreciated.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants