1

I have a wrapper of Parcelable generic type but Parcel constructing fails to compile because T class can not be determined generically

class MyItem<T : Parcelable> (val model: T) : Parcelable {
    constructor(parcel: Parcel) : 
      this(parcel.readParcelable(T::class.java.classLoader)) {

    }
}

Is there any solution to this case?

Enzokie
  • 7,365
  • 6
  • 33
  • 39
Jocky Doe
  • 2,041
  • 4
  • 17
  • 28
  • No idea if it's related, but why is your T declared `out` when you're passing it in for the constructor? Doesn't that mean it's not covariant and thus shouldn't be `out`? What happens if you remove `out`? – Paul Hicks Dec 20 '17 at 00:00
  • @PaulHicks true but it doesn't affect the issue – Jocky Doe Dec 20 '17 at 00:08
  • Yep didn't think so. Remember, T isn't a class, it's a formal parameter. You can use a [reified inline function](https://kotlinlang.org/docs/reference/inline-functions.html#reified-type-parameters) for this sort of thing, but that won't work in a secondary constructor. You would have to use a normal reified inline function in a factory, or more likely, on a companion object. – Paul Hicks Dec 20 '17 at 00:10
  • Could you please elaborate, why do you want such layout? For me it sounds like all potential callers will need to know type of `T` in advance... which makes the use of generics kind-of dubious. – user1643723 Dec 20 '17 at 02:21

3 Answers3

6

In order to get the whole picture here is what I ended up with:

The use case is one has a Parcelable generic instance let's call it model which should be completed with common properties of Parcelable wrapper in order to not pollute the model with extrinsic fields. For example Item wrapper.

In the example below the wrapper extra property gives us some type of index :

class Item<T : Parcelable> (val model: T, val index: Int ) : Parcelable {

    constructor(parcel: Parcel) :
        this(parcel.readParcelable(
          Item<T>::model.javaClass.classLoader),
          parcel.readInt()
        ) {}

    override fun writeToParcel(parcel: Parcel?, flag: Int) {
        parcel?.writeParcelable(model, 0)
        parcel?.writeInt(index)
    }

    override fun describeContents(): Int {
        return 0
    }

    companion object CREATOR : Parcelable.Creator<Item<Parcelable>> {
        override fun createFromParcel(parcel: Parcel): Item<Parcelable> {
            return Item(parcel)
        }

        override fun newArray(size: Int): Array<Item<Parcelable>?> {
            return arrayOfNulls(size)
        }
    }
}

So in the end we can have something like: Item(Person("John"), 0), Item(Person("Bill"), 1)...

class PersonPagerFragment() : BasePagerFragment<Person>() {
    companion object {
        fun newInstance(itemList: ArrayList<Item<Person>>) 
          : PersonPagerFragment {

            val args = Bundle()
            val fragment = PersonPagerFragment()
            args.putParcelableArrayList("items", itemList)
            fragment.arguments = args

            return fragment
        }
    }
}

extending class like:

class BasePagerFragment<T : Parcelable> : Fragment(){
    protected fun readBundle(bundle: Bundle?) {
        bundle.getParcelableArrayList<Item<T>>("items")    
    }
}
Jocky Doe
  • 2,041
  • 4
  • 17
  • 28
  • That works. You're not calling `createFromParcel` or `newArray` in any of the example code, how will those be used? The Builder pattern is often used to do this sort of thing, rather than a model / template object. Are external dependencies prevent you from using that this time? – Paul Hicks Dec 20 '17 at 19:54
  • @PaulHicks I'm not sure I follow your thought exactly but I think you mean that I don't use the static methods? The provided methods in the example aswell as `CREATOR` are minimum requirement to extend `Parcelable`. My guess is `putParcelableArrayList` and `getParcelableArrayList` or `putParcelable` and `getParcelable` methods use these. Unfortunately the source isn't downloaded on the API I use :( – Jocky Doe Dec 20 '17 at 20:19
  • Ah, there's a specific well-known `Parcelable` API. I didn't know that. It's probably not important for this question though. – Paul Hicks Dec 20 '17 at 22:58
  • 1
    @PaulHicks somewhat it's a part of the Android API as alternative to `Serializable` – Jocky Doe Dec 20 '17 at 23:31
1

You can use a reified inline function as a factory method to achieve this. I prefer to do this on a companion object. Here's an MCVE:

class MyItem<T> (val model: T) {
    companion object {
        inline fun <reified T> from(parcel : T) : MyItem<T> {
            return MyItem<T>(T::class.java.newInstance())
        }
    }
}
fun main(args: Array<String>) {
    val mi = MyItem.from("hi")
    println("mi is a ${mi::class}")
}
Paul Hicks
  • 13,289
  • 5
  • 51
  • 78
1

If you need to have a Parcel-type constructor, you could change that to be the primary constructor, and figure out the type of the MyItem then.

class Parcel

class MyItem(val parcel: Parcel) {
    inline fun <reified T> model() : T {
         // You code would be calling
         // `parcel.readParcelable(T::class.java.classLoader)`
        return T::class.java.newInstance() as T
    }
}
fun main(args: Array<String>) {
    // You don't ned to know the out type when constructing MyItem.
    val mi = MyItem(Parcel())

    // But you do need to know it when calling model()
    val model : String = mi.model()

    println("mi.model is a ${model::class}")
}
Paul Hicks
  • 13,289
  • 5
  • 51
  • 78