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.
Related
I'm working on a weather app for a class and I'm having some trouble with parcelable. I've looked at several other questions that are similar to this on StackOverflow, but haven't found an answer, so I wanted to throw my specific situation out there.
I have this Data class:
package com.example.weatherapp
import android.os.Parcel
import android.os.Parcelable
data class CurrentConditions(
val weather: List<WeatherCondition>,
val main: Currents?,
val name: String?
) : Parcelable {
constructor(parcel: Parcel) : this(
parcel.readParcelableList(List<WeatherCondition>(), WeatherCondition::class.java.classLoader), <-- This line is giving me trouble
parcel.readParcelable(Currents::class.java.classLoader),
parcel.readString()
) {
}
override fun writeToParcel(parcel: Parcel, flags: Int) {
parcel.writeParcelable(main, flags)
parcel.writeString(name)
}
override fun describeContents(): Int {
return 0
}
companion object CREATOR : Parcelable.Creator<CurrentConditions> {
override fun createFromParcel(parcel: Parcel): CurrentConditions {
return CurrentConditions(parcel)
}
override fun newArray(size: Int): Array<CurrentConditions?> {
return arrayOfNulls(size)
}
}
}
And on the line I've indicated, I'm trying to parcel a list of WeatherCondition objects, which is another parcelable data class:
package com.example.weatherapp
import android.os.Parcel
import android.os.Parcelable
data class WeatherCondition(
val main: String?,
val icon: String?
) : Parcelable {
constructor(parcel: Parcel) : this(
parcel.readString(),
parcel.readString()
) {
}
override fun writeToParcel(parcel: Parcel, flags: Int) {
parcel.writeString(main)
parcel.writeString(icon)
}
override fun describeContents(): Int {
return 0
}
companion object CREATOR : Parcelable.Creator<WeatherCondition> {
override fun createFromParcel(parcel: Parcel): WeatherCondition {
return WeatherCondition(parcel)
}
override fun newArray(size: Int): Array<WeatherCondition?> {
return arrayOfNulls(size)
}
}
}
The empty parentheses at the end of List<WeatherCondition>() have a red line on them. I'm not sure if I need something in the parentheses or if they shouldn't be there at all. If I remove the () then List<WeatherCondition> has the red line instead. Am I even close to on the right track? (This is my first time writing a mobile app, so I'm on a steep learning curve here).
Thanks for any help anyone can offer.
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 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
I am trying to write a parcelable data object to pass to from activityA to activityB in my android application.
My object is passing with all the data, except my arraylist of the class Available Service
data class AvailableService(val id: Int,
val name: String,
val description: String,
val price: Double,
val currency: String,
val imageUrl: String) : Parcelable {
companion object {
#JvmField #Suppress("unused")
val CREATOR = createParcel { AvailableService(it) }
}
protected constructor(parcelIn: Parcel) : this(parcelIn.readInt(),
parcelIn.readString(),
parcelIn.readString(),
parcelIn.readDouble(),
parcelIn.readString(),
parcelIn.readString())
override fun writeToParcel(dest: Parcel?, flags: Int) {
dest?.writeInt(id)
dest?.writeString(name)
dest?.writeString(description)
dest?.writeDouble(price)
dest?.writeString(currency)
dest?.writeString(imageUrl)
}
override fun describeContents() = 0
}
above is the available serviceClass, next I have Trip, which holds an arraylist of AvailableService.. I observed this in debug, it is successfully writing the arraylist, for some reason I have an issue with reading the data.
data class Trip(val id: String,
val status: String,
val orderedServices: ArrayList<OrderedService>) : Parcelable {
companion object {
#JvmField #Suppress("unused")
val CREATOR = createParcel { Trip(it) }
}
protected constructor(parcelIn: Parcel) : this(parcelIn.readString(),
parcelIn.readString(),
arrayListOf<OrderedService>().apply {
parcelIn.readArrayList(OrderedService::class.java.classLoader)
}
)
override fun writeToParcel(dest: Parcel?, flags: Int) {
dest?.writeString(id)
dest?.writeString(status)
dest?.writeList(orderedServices)
}
override fun describeContents() = 0
}
in case someone wonders what's the fun of the CREATOR does, code below:
inline fun <reified T : Parcelable> createParcel(
crossinline createFromParcel: (Parcel) -> T?): Parcelable.Creator<T> =
object : Parcelable.Creator<T> {
override fun createFromParcel(source: Parcel): T? = createFromParcel(source)
override fun newArray(size: Int): Array<out T?> = arrayOfNulls(size)
}
again, writing succeeds, but reading fails, I get an empty arraylist..
I think that part is the faulty one:
arrayListOf<OrderedService>().apply {
parcelIn.readArrayList(OrderedService::class.java.classLoader)
}
is there a different way to read/write arraylist? am I writing it wrong? reading it wrong?
thanks in advance for any help!
Replace:
arrayListOf<OrderedService>().apply {
parcelIn.readArrayList(OrderedService::class.java.classLoader)
}
with:
arrayListOf<OrderedService>().apply {
parcelIn.readList(this, OrderedService::class.java.classLoader)
}
Change
arrayListOf<OrderedService>().apply {
parcelIn.readArrayList(OrderedService::class.java.classLoader)
}
to
source.createTypedArrayList(OrderedService.CREATOR)
Change dest?.writeList(orderedServices)
to dest?.writeTypedList(orderedServices)
The creator method
companion object {
#JvmField
val CREATOR: Parcelable.Creator<Trip> = object : Parcelable.Creator<Trip> {
override fun createFromParcel(source: Parcel): Trip = Trip(source)
override fun newArray(size: Int): Array<Trip?> = arrayOfNulls(size)
}
}
For API 29 and above
Replace:
arrayListOf<OrderedService>().apply {
parcelIn.readArrayList(OrderedService::class.java.classLoader)
}
with
if (Build.VERSION.SDK_INT >= 29)
parcel.readParcelableList(this, OrderedService::class.java.classLoader)
else
parcel.readList(this as List<OrderedService>, OrderedService::class.java.classLoader)
}
and
override fun writeToParcel(dest: Parcel?, flags: Int) {
dest?.writeString(id)
dest?.writeString(status)
dest?.writeList(orderedServices)
}
with
override fun writeToParcel(dest: Parcel?, flags: Int) {
dest?.writeString(id)
dest?.writeString(status)
if (Build.VERSION.SDK_INT >= 29) {
dest?.writeParcelableList(orderedServices,flags)
} else {
dest?.writeList(orderedServices as List<OrderedService>)
}
}
If Parent and Child model have parcelable then used below one
Parent Model
Parcelable {
constructor(parcel: Parcel) : this(
parcel.createTypedArrayList(ImagesModel.CREATOR)
)
}
override fun writeToParcel(parcel: Parcel, flags: Int) {
parcel.writeTypedList(images)
}