I added Parcelable implementation for my class and get this code:
data class Category (val name:String, var factors:ArrayList<String>):Parcelable {
constructor(parcel: Parcel) : this(
parcel.readString(),
TODO("factors")) {
}
override fun writeToParcel(parcel: Parcel, flags: Int) {
parcel.writeString(name)
}
override fun describeContents(): Int {
return 0
}
companion object CREATOR : Parcelable.Creator<Category> {
override fun createFromParcel(parcel: Parcel): Category {
return Category(parcel)
}
override fun newArray(size: Int): Array<Category?> {
return arrayOfNulls(size)
}
}
}
As far as I understand I should write method for reading data from ArrayList. I've tried to use
parcel.readArrayList()
but can not understand, how to insert String as ClassLoader. Please help me to understand, how to make this object Parcelable.
Use the following within your Constructor:
parcel.createStringArrayList()
and within writeToParcel:
parcel.writeStringList(this.mTest)
Just a side node: If you're using Android Studio, just let it generate the Parcelable with the Parcelable code generator Plugin.
Read Parcelable
segments=new ArrayList<>();
in.readTypedList(segments,Segments.CREATOR);
Multi = in.readString();
And for writing
dest.writeTypedList(segments);
dest.writeString(Multi);
And it works like a charm
Related
I have an object list as follows:
data class MyProduct(
var id: String?,
var name: String?
) : Parcelable {
override fun writeToParcel(parcel: Parcel, flags: Int) {
parcel.writeString(id)
parcel.writeString(name)
}
override fun describeContents(): Int {
return 0
}
companion object CREATOR : Parcelable.Creator<MyProduct> {
override fun createFromParcel(parcel: Parcel) = MyProduct(
id = parcel.readString(),
name = parcel.readString(),
)
override fun newArray(size: Int): Array<MyProduct?> = arrayOfNulls(size)
}
}
MutableList<MyProduct>
I need to share this using an event broadcaster. I have a method as follows:
fun sendMyProduct(myProducts: MutableList<MyProduct>) {
intentBroadcaster.broadcastMessage(
event = EVENT_MY_PRODUCTS,
data = MyProductsUpdated(
myProducts = myProducts
)
)
}
MyProductsUpdated is a parcelable class.
data class MyProductsUpdated(
val myProducts: MutableList<MyProduct>
) : Event {
override fun writeToParcel(parcel: Parcel, flags: Int) {
parcel.writeList(myProducts)
}
override fun describeContents(): Int {
return 0
}
companion object CREATOR : Parcelable.Creator<MyProductsUpdated> {
override fun createFromParcel(parcel: Parcel) = MyProductsUpdated(
myProducts = parcel.readParcelable(MyProduct::class.java.classLoader)!!
)
override fun newArray(size: Int): Array<MyProductsUpdated?> = arrayOfNulls(size)
}
}
When I need to retrieve the data after broadcast I am doing the following:
private fun myProductsUpdated(action: String, extras: Bundle) = invokeCallbacks(action) {
extras.getParcelable<MyProductsUpdated>(Events.DATA)!!
}
I'm not entirely convinced that my handling for reading or writing the list as a parcelable is correct.
At the moment when I try to use this I experience the following crash:
java.lang.RuntimeException: Error receiving broadcast Intent
which is:
Caused by: android.os.BadParcelableException: ClassNotFoundException when unmarshalling: ��9
at android.os.Parcel.readParcelableCreator(Parcel.java:2556)
at android.os.Parcel.readParcelable(Parcel.java:2482)
at com.xx.services.MyProductUpdated$CREATOR.createFromParcel(MyProductsUpdated.kt:26)
at com.xx.services.events.MyProductsUpdated$CREATOR.createFromParcel(MyProductsUpdated.kt:24)
I believe the issue is to do with the fact that I am writing a 'list' but just reading a parcelable but I'm not quite sure how to resolve that. Any help would be greatly appreciated.
You should be using readList, not readParcelable. Read the contents into an existing list.
myProducts = mutableListOf<MyProduct>().apply {
parcel.readList(this, MyProduct::class.java.classLoader)
}
Also consider using the Parcelize plugin to save yourself some time.
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.
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
I'm getting started with Kotlin and trying to implement a parcelable object that receives a list of strings as parameter of the secondary constructor. However, I'm getting the error:
Cannot access '< this >' before super class constructor has been called
here is my code:
class StringChecker(val stringList : List<String>) : Parcelable {
var mStringList = stringList
constructor(parcel: Parcel) : this(parcel.readStringList(mStringList))
override fun writeToParcel(parcel: Parcel, flags: Int) {
parcel.writeStringList(mStringList)
}
override fun describeContents(): Int {
return 0
}
companion object CREATOR : Parcelable.Creator<StringChecker> {
override fun createFromParcel(parcel: Parcel): StringChecker {
return StringChecker(parcel)
}
override fun newArray(size: Int): Array<StringChecker?> {
return arrayOfNulls(size)
}
}
}
I understand that I cannot call the member variable mStringList before creating the object and that of course makes sense, however the readStringList method requires a string list parameter. How can I resolve this issue? Is this a problem of my design parcelable vs taking list as constructor parameter?
You can't use fields in secondary constructor. Use createStringArrayList() instead of readStringList(mStringList)
constructor(parcel: Parcel) : this(parcel.createStringArrayList())
For this specific case, you could write it as
constructor(parcel: Parcel) : this(mutableListOf<String>()) {
parcel.readStringList(mStringList)
}
But I would prefer just to make it a factory method, not a constructor. Especially since you need it for a factory method anyway. Namely:
companion object CREATOR : Parcelable.Creator<StringChecker> {
override fun createFromParcel(parcel: Parcel): StringChecker {
val stringList = mutableListOf<String>()
parcel.readStringList(stringList)
return StringChecker(stringList)
}
override fun newArray(size: Int): Array<StringChecker?> {
return arrayOfNulls(size)
}
}
Some more: you probably don't want to have both stringList and mStringList properties, as your code currently does. If it needs to be var, just do class StringChecker(var stringList : List<String>) instead. Also look at #Parcelize.
I am running into a weird problem . I have a class A which implements Parcelable interface in kotlin.
I am passing the array of class A from one activity to another no issues here.
var arrayOfA:Array<A> // just to tell the type assume the array is initialised with the value
intent.putExtra("array", arrayOfA)
But while receiving it in another activity , I am not able to assign it to variable of type Array it is asking me to assign it to Array when A is on type parcelable why I am not able to assign it the variable.
in second Activity
var arrayOfA:Array<A>?=null
arrayA=intent.get("array") as Array<A> // Problem here. Class cast exception
I am not able to understand why. Can some one help me here. I don't want to change the type of variable to Array as it as many inter depedencies.(The class here is just a demonstration)
========================================
class A(val a:String?,val b:String?):Parcelable {
constructor(parcel: Parcel) : this(
parcel.readString(),
parcel.readString()) {
}
override fun writeToParcel(parcel: Parcel, flags: Int) {
parcel.writeString(a)
parcel.writeString(b)
}
override fun describeContents(): Int {
return 0
}
companion object CREATOR : Parcelable.Creator<A> {
override fun createFromParcel(parcel: Parcel): A {
return A(parcel)
}
override fun newArray(size: Int): Array<A?> {
return arrayOfNulls(size)
}
}
}
I ran into the same problem. This is how I was able to workaround it:
arrayOfA = intent.getParcelableArrayExtra("array").map { it as A }.toTypedArray()
For some reason this does not work:
arrayOfA = intent.getParcelableArrayExtra("array") as Array<A>
That unfortunately exhibits a warning
Unchecked cast: Array<(out) Parcelable!>! to Array)
and runtime exception
java.lang.ClassCastException: android.os.Parcelable[] cannot be cast
to com.company.app.A[]
Bundle.getParcelableArray (and Intent.getParcelableArrayExtra) return Parcelable[]. You can't cast Parcelable[] as MyClass[]. You can cast an element of Parcelable[] as MyClass.
Use Bundle.getParcelableArrayList (or its Intent counterpart) instead, because it has a generic type.
var values: List<MyClass> = listOf(...)
intent.putParcelableArrayListExtra("key", values.asArrayList())
values = intent.getParcelableArrayListExtra("key")!!
fun <E> List<E>.asArrayList(): ArrayList<E> = this as? ArrayList<E> ?: ArrayList(this)
You need to use getParcelableArrayExtra to retrieve Parcelable objects as in
arrayA = intent.getParcelableArrayExtra("array") as Array<A?>
Make class A as Nullable all over, because you can't cast Nullable to Non Nullable in Kotlin
creator should be like below
companion object CREATOR : Parcelable.Creator<A?> {
override fun createFromParcel(parcel: Parcel): A? {
return A(parcel)
}
override fun newArray(size: Int): Array<A?> {
return arrayOfNulls(size)
}
}