Get generics class loader for parsing nested Parcelable generic field - android

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

Related

Android - Custom NavType for List<Enum<*>>

I am trying to create a custom NavType for Enum with below code:
class EnumListType<T : Enum<T>> : NavType<List<T>>(true) {
#Suppress("TYPE_MISMATCH_WARNING")
override fun get(bundle: Bundle, key: String): List<T> {
val found = bundle.getStringArrayList(key)
return found?.map { enumValueOf(it) } ?: emptyList()
}
#Suppress("TYPE_MISMATCH_WARNING")
override fun parseValue(value: String): List<T> {
return value.substring(1, value.length - 1)
.split(',')
.map { enum -> java.lang.Enum.valueOf(T::class.java, enum) }
}
override fun put(bundle: Bundle, key: String, value: List<T>) {
bundle.putStringArrayList(key, values.map { it.name } as ArrayList<String>)
}
}
Both for get and parseValue, my code fails to compile with below error:
Cannot use 'T' as reified type parameter. Use a class instead.
Is it even possible to achieve a solution for this? If yes, what's wrong with the above code and how to fix it up?
class CustomType<T : Parcelable> : NavType<T>(isNullableAllowed = false) {
override fun get(bundle: Bundle, key: String): T? = bundle.getParcelable(key)
override fun parseValue(value: String): T {
val type: Type = object : TypeToken<Class<T>>() {}.type
return Gson().fromJson(value, type)
}
override fun put(bundle: Bundle, key: String, value: T) {
bundle.putParcelable(key, value)
}
}

how to implement parcelable to ArrayList of Mutable Map?

I have this property in my class that the data type is ArrayList<MutableMap<String,Any>> , but I am confused what should I write in parcel constructor and also in writeToParcel method ?
class User() : Parcelable {
var uid : String = ""
var upcomingEvents : ArrayList<MutableMap<String,Any>> = ArrayList()
constructor(parcel: Parcel) : this() {
uid = parcel.readString() ?: ""
upcomingEvents = ....... // <-- what should I write in here ?
}
override fun writeToParcel(parcel: Parcel, flags: Int) {
parcel.writeString(uid)
parcel........ // <--- and also here ???
}
java or kotlin is okay
As Mohammed Alaa already mentioned, in Kotlin you can use the #Parcelize annotation to have all code generated automatically:
import android.os.Parcel
import android.os.Parcelable
import kotlinx.android.parcel.Parcelize
#Parcelize
data class User(var uid: String, var upcomingEvents: List<MutableMap<String, Any>>) :
Parcelable
If you use the Any class, this approach will not work. In that case you could go for changing from Any to a class that is supported by Parcelize or write the code yourself. Something like this:
data class User(var uid: String, var upcomingEvents: List<MutableMap<String, Any>>) :
Parcelable {
constructor(parcel: Parcel) : this(
parcel.readString() ?: "",
readUpcomingEvents(parcel)
)
override fun writeToParcel(parcel: Parcel, flags: Int) {
parcel.writeString(uid)
parcel.writeList(upcomingEvents)
}
override fun describeContents(): Int {
return 0
}
companion object CREATOR : Parcelable.Creator<User> {
override fun createFromParcel(parcel: Parcel): User {
return User(parcel)
}
override fun newArray(size: Int): Array<User?> {
return arrayOfNulls(size)
}
private fun readUpcomingEvents(parcel: Parcel): List<MutableMap<String, Any>> {
val list = mutableListOf<MutableMap<String, Any>>()
parcel.readList(list as List<*>, MutableMap::class.java.classLoader)
return list
}
}
}

ArrayList<ArrayList<String>> in Parcelable object kotlin

Array of arrays of strings needs to be parcelized. The object is like so
data class Foo (
#SerializedName("bar") val bar: ArrayList<ArrayList<String>>,
)
It doesn't exactly need to be ArrayList. Array can also used.
data class Foo (
#SerializedName("bar") val bar: Array<Array<String>>,
)
Whichever easier is ok to map this json data
{
"bar": [
["a", "b"],
["a1", "b2", "c2"],
["a3", "b34", "c432"]
]
}
Using kotlin experimental Parcelize crashes the app when it's compiled with progaurd
How is it written in "writeToParcel" and read in "constructor"?
data class Foo (
#SerializedName("bar") val bar: ArrayList<ArrayList<String>>,
) : Parcelable {
constructor(source: Parcel) : this(
// ?????
)
override fun writeToParcel(dest: Parcel, flags: Int) = with(dest) {
// ?????
}
}
You can't directly create Parcelable for List of List directly, so one solution is to make one subclass of your desired List as Parcelable and take it as you final list type. How? check out below :
Let's first create our internal List of String class like below :
class StringList() : ArrayList<String>(), Parcelable {
constructor(source: Parcel) : this() {
source.createStringArrayList()
}
override fun describeContents() = 0
override fun writeToParcel(dest: Parcel, flags: Int) {
dest.writeStringList(this#StringList)
}
companion object {
#JvmField
val CREATOR: Parcelable.Creator<StringList> = object : Parcelable.Creator<StringList> {
override fun createFromParcel(source: Parcel): StringList = StringList(source)
override fun newArray(size: Int): Array<StringList?> = arrayOfNulls(size)
}
}
}
What we've done here is created our ArrayList<String> parcelable so that we can use it at any endpoint.
So final data class would be having following implementation :
data class Foo(#SerializedName("bar") val bar: List<StringList>) : Parcelable {
constructor(source: Parcel) : this(
source.createTypedArrayList(StringList.CREATOR)
)
override fun describeContents() = 0
override fun writeToParcel(dest: Parcel, flags: Int) = with(dest) {
writeTypedList(bar)
}
companion object {
#JvmField
val CREATOR: Parcelable.Creator<Foo> = object : Parcelable.Creator<Foo> {
override fun createFromParcel(source: Parcel): Foo = Foo(source)
override fun newArray(size: Int): Array<Foo?> = arrayOfNulls(size)
}
}
}
Note: It's simple implementation based on O.P., you can make any customization based on your requirement.

Read List of List from Parcelable in Kotlin constructor android

I want to basically read the List of List from the Parcel in Kotlin constructor, I have my class structured like this:
data class Sports(
var media: List<List<MediaObject>>?,
var likes: Int) : Parcelable {
constructor(parcel: Parcel) : this(
TODO("media"),
parcel.readInt(),
)
override fun writeToParcel(parcel: Parcel, flags: Int) {
if (media == null || media!!.isEmpty()) {
parcel.writeInt(0)
} else {
parcel.writeInt(media!!.size)
for (mediaObjects in media!!) {
parcel.writeTypedList(mediaObjects)
}
}
parcel.writeInt(likes)
}
override fun describeContents(): Int {
return 0
}
companion object CREATOR : Parcelable.Creator<Sports> {
override fun createFromParcel(parcel: Parcel): Sports {
return Sports(parcel)
}
override fun newArray(size: Int): Array<Sports?> {
return arrayOfNulls(size)
}
}}
I have a List<List<MediaObject>> which i want to read from the parcel, how can we achieve this, i guess we can have some inline functions but not sure how we'll do this ?
Kotlin automatically adds TODO if its List<List<>> type object.
In Kotlin 1.1.4 was added Parcelable support with #Parcelize annotation
It's more convenient way to deal with it
Check it out:
https://kotlinlang.org/docs/tutorials/android-plugin.html#parcelable

"Parcelable protocol requires a Parcelable.Creator object called CREATOR" (I do have CREATOR) - in Kotlin

I get the Error message "Parcelable protocol requires a Parcelable.Creator object called CREATOR on class .....", but I do have a Creator and I don't know what is wrong with it. I copied it from https://developer.android.com/reference/android/os/Parcelable and changed the class name to fit my code.
I suspect that the (automatic) conversion from Java to Kotlin wasn't perfect (or to be precise: did it slightly different than it would be needed), but I don't know what the problem is exactly.
There is a Thread with the same Error Message (Parcelable protocol requires a Parcelable.Creator object called CREATOR (I do have CREATOR)) but the problem there was that
"fun writeToParcel" did not write in in the same order as it is read in "fun DataHandler". That is not the Problem in my case as it's in the same order.
Another answer there pointed out that it could be a problem that the function needs to be static. However, Kotlin has no "static" function. I read that it's done with "Companion object". I tried that (see below), but it threw another error - and I'm not sure if it would even work.
class DataHandler : Parcelable {
var player1name = ""
var player1color = 0
//main constructor
fun DataHandler(player1name: String, player1color: Int) {
this.player1name = player1name
this.player1color = player1color
}
//write object values to parcel for storage
override fun writeToParcel(dest: Parcel, flags: Int) {
//write all properties to the parcle
dest.writeString(player1name)
dest.writeInt(player1color)
}
//constructor used for parcel
fun DataHandler(parcel: Parcel) {
//read and set saved values from parcel
player1name = parcel.readString()
player1color = parcel.readInt()
}
//creator - used when un-parceling our parcle (creating the object)
val CREATOR: Parcelable.Creator<DataHandler> = object : Parcelable.Creator<DataHandler> {
override fun createFromParcel(parcel: Parcel): DataHandler {
return DataHandler(parcel) as DataHandler
}
override fun newArray(size: Int): Array<DataHandler?> {
return arrayOfNulls<DataHandler>(size)
}
}
//return hashcode of object
override fun describeContents(): Int {
return hashCode()
}
}
This is the sending activity:
val intentPickPlayer = Intent(this, PlayGame::class.java)
var dataHandler = DataHandler()
dataHandler.player1name = "testing"
intentPickPlayer.putExtra("data", dataHandler)
startActivity(intentPickPlayer)
This is the receiving activity:
class PlayGame : Activity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.playgame)
val test = intent.getParcelableExtra<DataHandler>("data")
Toast.makeText(this, test.player1name, Toast.LENGTH_SHORT).show()
}
Like said above: I tried to make the CREATOR static by putting it into a companion object (apparently that's the way it works in Kotlin, but that produces another error (and I'm not sure if it fixes the first problem)
companion object {
//creator - used when un-parceling our parcle (creating the object)
val CREATOR: Parcelable.Creator<DataHandler> = object : Parcelable.Creator<DataHandler> {
override fun createFromParcel(parcel: Parcel): DataHandler {
//HERE COMES AN ERROR: parcel has a red underlining and it says: "too many Arguments for public constructor DataHandler()
return DataHandler(parcel) as DataHandler
}
override fun newArray(size: Int): Array<DataHandler?> {
return arrayOfNulls<DataHandler>(size)
}
}
In Kotlin constructors are defined by the constructor keyword - https://kotlinlang.org/docs/reference/classes.html
See also https://kotlinlang.org/docs/reference/classes.html#secondary-constructors for info on secondary constructors. Secondary constructors need to delegate to the primary one.
Default constructors are usually defined after the class name and properties defined as part of it:
class DataHandler(var player1name: String, var player1color: Int) : Parcelable {
//write object values to parcel for storage
override fun writeToParcel(dest: Parcel, flags: Int) {
//write all properties to the parcle
dest.writeString(player1name)
dest.writeInt(player1color)
}
//constructor used for parcel
constructor(parcel: Parcel) : this(
//read and set saved values from parcel
player1name = parcel.readString(),
player1color = parcel.readInt())
companion object {
#JvmField
//creator - used when un-parceling our parcle (creating the object)
val CREATOR: Parcelable.Creator = object : Parcelable.Creator {
override fun createFromParcel(parcel: Parcel): DataHandler {
return DataHandler(parcel) as DataHandler
}
override fun newArray(size: Int): Array {
return arrayOfNulls(size)
}
}
}
//return hashcode of object
override fun describeContents(): Int {
return hashCode()
}
}

Categories

Resources