16

I have a data class in Kotlin hat is using the @Parcelize annotation for easy parcelization. Thing is I now want to pass a function to this class and I do not really know how to make the function not be considered during parceling.

This is my data class:

@Parcelize
data class GearCategoryViewModel(
        val title: String,
        val imageUrl: String,
        val categoryId: Int,
        val comingSoon: Boolean,
        @IgnoredOnParcel val onClick: (gearCategoryViewModel: GearCategoryViewModel) -> Unit
) : DataBindingAdapter.LayoutViewModel(R.layout.gear_category_item), Parcelable

I tried using @IgnoredOnParcel and @Transient without success.

This is the compile error I get:

Error:(20, 39) Type is not directly supported by 'Parcelize'. Annotate the parameter type with '@RawValue' if you want it to be serialized using 'writeValue()'

And this @RawValue annotation does not work either.

  • **I now want to pass a function to this class** What do you need that and what are you trying to acheive – Raghunandan Mar 20 '18 at 12:11
  • @Raghunandan I want this class to receive an implementation of a function so it can execute it through data-binding. But I also need this class to be parceleable, so its data can be sent to other activities, etc. – Felipe Ribeiro R. Magalhaes Mar 20 '18 at 12:18
  • It'd probably be more useful to create another object with title, imageUrl, categoryId and comingSoon and then make this object Parcelable and pass this same object to your viewModel. `data class GearCategoryViewModel(val someObject: parcelableObject, val onClick: (gearCategoryViewModel: GearCategoryViewModel) -> Unit)` That way you are not trying to serialise a `Function`. – Andrew G Aug 09 '19 at 04:50

4 Answers4

10

Just cast lambda to serializable, and then create object from

@Parcelize
data class GearCategoryViewModel(
        val title: String,
        val imageUrl: String,
        val categoryId: Int,
        val comingSoon: Boolean,
        @IgnoredOnParcel val onClick: Serializable
) : DataBindingAdapter.LayoutViewModel(R.layout.gear_category_item), Parcelable {

    fun onClicked() = onClick as (gearCategoryViewModel: GearCategoryViewModel) -> Unit

    companion object {
        fun create(
                title: String,
                imageUrl: String,
                categoryId: Int,
                comingSoon: Boolean,
                onClick: (gearCategoryViewModel: GearCategoryViewModel) -> Unit
        ): GearCategoryViewModel = GearCategoryViewModel(
                title,
                imageUrl,
                categoryId,
                comingSoon,
                onClick as Serializable
        )
    }
}
Artur Dumchev
  • 1,252
  • 14
  • 17
4
@Parcelize
data class GearCategoryViewModel(
        val title: String,
        val imageUrl: String,
        val categoryId: Int,
        val comingSoon: Boolean,
        val onClick: @RawValue (gearCategoryViewModel: GearCategoryViewModel) -> Unit
) : DataBindingAdapter.LayoutViewModel(R.layout.gear_category_item), Parcelable

Use @RawValue with the onClick parameter.

apurv thakkar
  • 8,608
  • 3
  • 14
  • 19
0

I know this isn't exactly a real answer but I would do it like this. Replace your function with class like this:

open class OnClick() : Parcelable {

    companion object {
        @JvmStatic
        fun fromLambda(func: (GearCategoryViewModel) -> Unit) = object : OnClick() {
            override fun invoke(param: GearCategoryViewModel) = func(param)
        }

        @JvmField
        val CREATOR = object : Parcelable.Creator<OnClick> {
            override fun createFromParcel(parcel: Parcel) = OnClick(parcel)

            override fun newArray(size: Int) = arrayOfNulls<OnClick>(size)
        }
    }

    constructor(parcel: Parcel) : this()

    open operator fun invoke(param: GearCategoryViewModel) {
        throw UnsupportedOperationException("This method needs to be implemented")
    }

    override fun writeToParcel(parcel: Parcel, flags: Int) = Unit

    override fun describeContents() = 0
}

And use it like this:

val onClick = OnClick.fromLambda { vm -> /* do something*/ }
onClick(viewModel);
TheKarlo95
  • 1,144
  • 9
  • 17
  • It wont work, as u do not write/read anything to/from parcel. But you can force it to work by saving lambda as Serializable into parcel. – Artur Dumchev Jun 22 '18 at 16:11
0

The premise of the question doesn't really make sense.

You can't deserialise this object if you don't serialise all of its properties.

You seem to want onClick to be null if this is deserialised, but even if that was possible, it'd throw a NPE because this property is not marked nullable.

Consider changing your architecture so you only serialise simple objects (POJOs or data classes) and not Functions.

Andrew G
  • 2,596
  • 2
  • 19
  • 26