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?
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")
}
}
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}")
}
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}")
}