I have a class that has a member variable of type ArrayList<>. Both classes implement parcelable. I'm stuck on how to complete the class that has a reference to the other class.
Here is what I have:
data class Tab (val name: String, val title: String, val color: String, val sections: ArrayList<Section>) : Parcelable {
constructor(parcel: Parcel) : this(
parcel.readString(),
parcel.readString(),
parcel.readString(),
parcel.readTypedList<Section>(sections, Section.CREATOR))
override fun writeToParcel(dest: Parcel?, flags: Int) {
dest?.writeString(name)
dest?.writeString(title)
dest?.writeString(color)
dest?.writeTypedList<Section>(sections)
}
override fun describeContents(): Int {
return 0
}
companion object CREATOR : Parcelable.Creator<Tab> {
override fun createFromParcel(parcel: Parcel): Tab {
return Tab(parcel)
}
override fun newArray(size: Int): Array<Tab?> {
return arrayOfNulls(size)
}
}
}
Notice how this class has a value called sections that is an ArrayList<Section>. I need to write that variable to a parcelable, but it's not working.
For reference, here is the Section class. I think this one is OK:
data class Section(val type: String, val text: String, val imageName: String) : Parcelable {
constructor(parcel: Parcel) : this(
parcel.readString(),
parcel.readString(),
parcel.readString())
override fun writeToParcel(dest: Parcel?, flags: Int) {
dest?.writeString(type)
dest?.writeString(text)
dest?.writeString(imageName)
}
override fun describeContents(): Int {
return 0
}
companion object CREATOR : Parcelable.Creator<Section> {
override fun createFromParcel(parcel: Parcel): Section {
return Section(parcel)
}
override fun newArray(size: Int): Array<Section?> {
return arrayOfNulls(size)
}
}
}
It's the readTypedList and writeTypedList lines that are failing.
Thanks for any help.
First solution
#Suppress("UNCHECKED_CAST")
constructor(parcel: Parcel): this(parcel.readString(), parcel.readString(), parcel.readString(), parcel.readArrayList(Tab::class.java.classLoader) as ArrayList<Section>)
Second solution
constructor(parcel: Parcel): this(parcel.readString(), parcel.readString(), parcel.readString(), ArrayList<Section>()){
parcel.readTypedList(sections, Section.CREATOR)
}
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.
I have already a Parcelable data class which is used across the module:
#Keep
data class TotalProductValue(val header: String?, val prices: Pricing?, val cta: Cta?) : Parcelable {
constructor(parcel: Parcel) : this(
parcel.readString(),
parcel.readParcelable(Pricing::class.java.classLoader),
parcel.readParcelable(Cta::class.java.classLoader),
)
override fun writeToParcel(parcel: Parcel, flags: Int) {
parcel.writeString(header)
parcel.writeString(subHeader)
parcel.writeParcelable(prices, flags)
parcel.writeParcelable(cta, flags)
}
override fun describeContents(): Int {
return 0
}
companion object CREATOR : Parcelable.Creator<TotalProductValue> {
override fun createFromParcel(parcel: Parcel): TotalProductValue {
return TotalProductValue(parcel)
}
override fun newArray(size: Int): Array<TotalProductValue?> {
return arrayOfNulls(size)
}
}
}
Now I want to add one more parameter as below without impacting other places where it's used, I tried default value and parcel.dataAvail() below but it didn't work. Is there any work around without creating another data class ?
#Keep
data class TotalProductValue(val header: String?, val subHeader: String? = null, val prices: Pricing?, val cta: Cta?) : Parcelable {
constructor(parcel: Parcel) : this(
parcel.readString(),
if (parcel.dataAvail() > 0) parcel.readString() else null,
parcel.readParcelable(Pricing::class.java.classLoader),
parcel.readParcelable(Cta::class.java.classLoader),
)
override fun writeToParcel(parcel: Parcel, flags: Int) {
parcel.writeString(header)
parcel.writeString(subHeader)
parcel.writeParcelable(prices, flags)
parcel.writeParcelable(cta, flags)
}
override fun describeContents(): Int {
return 0
}
companion object CREATOR : Parcelable.Creator<TotalProductValue> {
override fun createFromParcel(parcel: Parcel): TotalProductValue {
return TotalProductValue(parcel)
}
override fun newArray(size: Int): Array<TotalProductValue?> {
return arrayOfNulls(size)
}
}
}
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
}
}
}
I have a parent class in Kotlin like this
open class Parent(
var name: String,
var dose: JsonElement?,
val other: String?) {
constructor(name: String, dose: JsonElement?)
: this(
name = name.toLowerCase(),
dose = dose,
other = null
)
}
And its child class like this
class Child(
val type: String,
name: String,
dose: JsonElement?
) : Parent(name, dose), Parcelable {
constructor(parcel: Parcel) : this(
parcel.readString() ?: "",
parcel.readString() ?: "",
Gson().fromJson(parcel.readString(), JsonElement::class.java))
override fun writeToParcel(parcel: Parcel, flags: Int) {
parcel.writeString(type)
parcel.writeString(name)
parcel.writeString(Gson().toJson(dose))
}
override fun describeContents(): Int {
return 0
}
companion object CREATOR : Parcelable.Creator<Child> {
override fun createFromParcel(parcel: Parcel): Child {
return Child(parcel)
}
override fun newArray(size: Int): Array<Child?> {
return arrayOfNulls(size)
}
}
}
My problem here is when I pass object of child class as parcelable only child class properties are propagated. All base class properties like name and dose are null after object is de-serialized.
I have cross-verified order of reading and writing and also verified that values are being written correctly at the time of serialization. Let me know if more information is needed from my side.
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)
}