I'm struggling how can I pass an array with objects from activity to service by intent. After debugging i can say that my service doesnt even start because of error java.lang.RuntimeException: Parcel: unable to marshal value (Item(id=0, data=2023-01-02T02:07:11.051, message=123, isValid=false.
listViewModel.itemList.observe(this) {
listAdapter.setList(it)
recyclerView.adapter = listAdapter
val i = Intent(this, FileService::class.java)
val bundle = Bundle()
bundle.putSerializable("data", it)
i.putExtras(bundle)
startService(i)
}
and my data class
#Serializable
data class Item(val id: Int,
val data: LocalDateTime? = Clock.System.now().toLocalDateTime(TimeZone.UTC),
val message: String,
var isValid: Boolean? = false)
As you can see im not using parcelable. I have no idea what to do. I have tried option without bundle.
You are getting this error because of you used #Serializable instead of Serializable class.
You need to make Item class Serializable. To make Item class serializable I had to add : Serializable at the end of class to make is Serializable. Just like this
class ExampleClass(val price: Double, val discountedPrice: Double) : Serializable
Make sure to import Serializable like this
import java.io.Serializable
Related
We had a RuntimeException while we put a safeArgs into the navigation graph and this crash didn't give us any more guide to fix it.
In the first impression, we were following this to ensure that those classes were Parcelize or not.
Therefore, those classes were parceled correctly and everything seems right.
After many searches and investigations into our codes, I found that SafeArgs class exists in a sealed-class file.
Considering to this point that we can't allocate the parceled annotation to the sealed classes, we've decided to move our class outside of that sealed .kt file.
Although, I found the main reason for that runtime crash was this cause.
Also, I've provided the wrong and correct cases below, We hope this will be helpful to others:
We've moved the UserFavorite and its subclass, outside of the Response.kt file,
sealed class Response {
.
.
#Parcelize
data class UserFavorite(
#SerializedName("title") val title: String,
#SerializedName("itemType") val itemType: String,
#SerializedName("emptyIcon") val emptyIcon: String,
#SerializedName("_texts") val texts: UserFavoriteTexts
) : Response(), Parcelable
#Parcelize
data class UserFavoriteTexts(
#SerializedName("hintMessage") val hintMessage: String,
#SerializedName("add") val add: String,
#SerializedName("remove") val remove: String,
#SerializedName("edit") val edit: String
): Parcelable
.
.
}
into an independent file for that: UserFavorite.kt
#Parcelize
data class UserFavorite(
#SerializedName("title") val title: String,
#SerializedName("itemType") val itemType: String,
#SerializedName("emptyIcon") val emptyIcon: String,
#SerializedName("_texts") val texts: UserFavoriteTexts
) : Response(), Parcelable
#Parcelize
data class UserFavoriteTexts(
#SerializedName("hintMessage") val hintMessage: String,
#SerializedName("add") val add: String,
#SerializedName("remove") val remove: String,
#SerializedName("edit") val edit: String
): Parcelable
and, Respons.kt
sealed class Response {
.
.
.
.
}
I am creating a generic, abstract class like this:
abstract class BaseDialogFragment<T: Parcelable> : DialogFragment()
Trying to implement this class as
class MyDialogFragment : BaseDialogFragment<String>()
gives me
Type argument is not within its bounds Expected: Parcelable Found: String
for the String in BaseDialogFragment<String>().
So, how can I use String as a value for T? Is my condition T: Parcelable somehow wrong, if I want Tto be a parcelable type?
So, how can I use String as a value for T?
You can't. String is not Parcelable, because Parcel already knows how to handle String. Similarly, you cannot use ByteArray for T, or Int, or Boolean.
In my case I passed a list by
val list = bundle.getParcelableArrayList<SomeClass<String>>("key") ?: listOf()
Make sure SomeClass is Parcelable (all it's fields are parcelable too):
#Parcelize
data class SomeClass<T>(
val text: String,
...
) : Parcelable
I have a sealed class like so:
sealed class SealedClass {
object Object1 : SealedClass()
object Object2 : SealedClass()
object Object3 : SealedClass()
data class DataClass(val sealedClass: SealedClass, val anotherDataType: AnotherDataType? = null)
}
I would like to pass my data class in a Bundle like we normally pass values to a new fragment like so:
#JvmStatic
fun newInstance(dataClass: DataClass): Fragment {
val fragment = Fragment()
val args = Bundle(1)
args.putParcelable("DATA_CLASS", dataClass)
fragment.arguments = args
return fragment
}
I'm not sure how to go about this. So far what I've read is that people use an #Parcelize annotation, which is an experimental feature of Kotlin that I'm trying to avoid. Another approach is to extend the data class by Parcelable and implement the Parcelable methods, but since I use custom classes as parameters in the DataClass (for instance, SealedClass), I don't know how to read/write those values inside Parcelable implementation. Is this not a right approach to go about it?
I think this can be simpler now with recent Kotlin using Parcelable:
#Parcelize
data class TimeSeries(
val sourceInfo: SourceInfo? = null,
val variable: Variable? = null,
val values: List<Value_>? = null,
val name: String? = null
) : Parcelable
Then pass it in your bundle:
val intent = Intent(context, DetailsActivity::class.java).apply {
putExtra(MY_DATA, mydata[position])
}
context.startActivity(intent)
Then bring it in through your bundle:
mydata = intent?.getParcelableExtra<TimeSeries>(MY_DATA)
If you want instead to pass a Bundle you can also just use bundleOf(MY_DATA to mydata[position]) when putting the Extra, and intent?.getBundleExtra(MY_DATA)?.getParcelable<TimeSeries>(MY_DATA) when getting it, but looks like adding another layer.
If you want to transform the sealed class as parcelable, you can do the following:
sealed class SealedClass : Parcelable {
#Parcelize
object Object1 : SealedClass()
#Parcelize
object Object2 : SealedClass()
#Parcelize
object Object3 : SealedClass()
#Parcelize
data class DataClass(val sealedClass: SealedClass, val anotherDataType: AnotherDataType? = null) : SealedClass()
}
Serializable while using reflection and causing a bit more garbage collection, is easier to implement.
I find it easiest to use GSON.
https://github.com/google/gson
First, add it to your data class like this:
data class TimeSeries(
#SerializedName("sourceInfo")
val sourceInfo: SourceInfo? = null,
#SerializedName("variable")
val variable: Variable? = null,
#SerializedName("values")
val values: List<Value_>? = null,
#SerializedName("name")
val name: String? = null
) : Serializable
Then pass it in your bundle:
val intent = Intent(context, DetailsActivity::class.java).apply {
putExtra(MY_DATA, Gson().toJson(mydata[position]))
}
context.startActivity(intent)
Then bring it in through your bundle:
mydata = Gson().fromJson(intent?.extras?.getString(MY_DATA), TimeSeries::class.java)
I have this set of data classes, all in the same file:
#Parcelize data class Response(val RecordView: RecordView):Parcelable
#Parcelize data class RecordView(val Records: List<Records>):Parcelable
#Parcelize data class Gid(val Value: String):Parcelable
#Parcelize data class Features(val Fields: List<Fields>, val FeatureName: String, val Title: String):Parcelable
#Parcelize data class Fields(val Label: String, val Value: String, val FieldName: String, val Features: List<Features>):Parcelable
#Parcelize data class Records(val Gid: Gid, val ImageIds: List<String>, val Fields: List<Fields>, val Features: List<Features>):Parcelable
I then send the Response object as an intent for the next activity:
val record = intent?.getParcelableExtra<Records>(RECORD)
but the next activity then throws this:
java.lang.NullPointerException: Attempt to invoke interface method 'int java.util.Collection.size()' on a null object reference
at entities.Features.writeToParcel(Features.kt)
at entities.Records.writeToParcel(Records.kt)
at android.os.Parcel.writeParcelable(Parcel.java:1496)
at android.os.Parcel.writeValue(Parcel.java:1402)
at android.os.Parcel.writeArrayMapInternal(Parcel.java:724)
at android.os.BaseBundle.writeToParcelInner(BaseBundle.java:1408)
at android.os.Bundle.writeToParcel(Bundle.java:1157)
at android.os.Parcel.writeBundle(Parcel.java:764)
at android.content.Intent.writeToParcel(Intent.java:8687)
at android.app.ActivityManagerProxy.startActivity(ActivityManagerNative.java:3082)
at android.app.Instrumentation.execStartActivity(Instrumentation.java:1518)
at android.app.Activity.startActivityForResult(Activity.java:4225)
This same process works when I just send a more basic class (just Strings in the parameters), using the same #Parcelize.
And yes, I turned on experimental on my gradle file.
Any ideas? Thanks
I think your API should not response -> Null Object
because data intent can't write Null (write to parcel)
when u use #Parcelize
https://youtrack.jetbrains.com/oauth?state=%2Fissue%2FKT-20032
fun launchNextScreen(context: Context, people: People): Intent {
val intent = Intent(context, NextScreenActivity::class.java)
intent.putExtra(EXTRA_PEOPLE, (Parcelable) people)
//intent.putExtra(EXTRA_PEOPLE, people as Parcelable)
//intent.putExtra(EXTRA_PEOPLE, people)
// tried above all three ways
return intent
}
I tried the above code to pass an instance of the People class via intent using kotlin, but I am getting an error.
What am I doing wrong?
First, make sure the People class implements the Serializable interface:
class People : Serializable {
// your stuff
}
Inner fields of People class must also implement the Serializable interface, otherwise you'll get runtime error.
Then it should work:
fun launchNextScreen(context: Context, people: People): Intent {
val intent = Intent(context, NextScreenActivity::class.java)
intent.putExtra(EXTRA_PEOPLE, people)
return intent
}
To receive people back from Intent you'll need to call:
val people = intent.getSerializableExtra(EXTRA_PEOPLE) as? People
Implement Serializable in the object:
data class Object (
var param1: Int? = null,
var param2: String? = null
) : Serializable
or
class Object : Serializable {
var param1: Int? = null,
var param2: String? = null
}
Then, you can pass the object using Intent:
val object = Object()
...
val intent = Intent(this, Activity2::class.java)
intent.putExtra("extra_object", object as Serializable)
startActivity(intent)
Finally, in Activity2 you get the object with:
val object = intent.extras.get("extra_object") as Object
Found a better way of doing this:
In your gradle:
apply plugin: 'org.jetbrains.kotlin.android.extensions'
android {
androidExtensions {
experimental = true
}
}
In your data class:
#Parcelize
data class Student(val id: String, val name: String, val grade: String) : Parcelable
In source activity:
val intent = Intent(context, Destination::class.java)
intent.putExtra("student_id", student)
context.startActivity(intent)
In destination activity:
student = intent.getParcelableExtra("student_id")
Parcelize is no longer experimental, as of Kotlin 1.3.60+!
Define a data class, adding #Parcelize annotation and extending Parcelable:
#Parcelize
data class People(val id: String, val name: String) : Parcelable
To add to intent:
val intent = Intent(context, MyTargetActivity::class.java)
intent.putExtra("people_data", student)
context.startActivity(intent)
In MyTargetActivity:
val people: People = intent.getParcelableExtra("people_data")
If you haven't yet, enable extensions in your app's build.gradle. This also enables data binding and other great advanced features
apply plugin: 'kotlin-android-extensions'
I found a better way to pass an object from one activity to another using Parcelable, it is faster than Serialization
Android: Difference between Parcelable and Serializable?
first, add these lines of code inside build.gradle(app)
apply plugin: 'kotlin-android-extensions' in app.grable
#Parcelize
class Model(val title: String, val amount: Int) : Parcelable
passing with Intent--->
val intent = Intent(this, DetailActivity::class.java)
intent.putExtra(DetailActivity.EXTRA, model)
startActivity(intent)
Getting from intent --->
val model: Model = intent.getParcelableExtra(EXTRA)
This post might be useful for what you intend to do:
Is there a convenient way to create Parcelable data classes in Android with Kotlin?
Basically, kotlin provides some little extras:
#Parcelize
class User(val firstName: String, val lastName: String) : Parcelable
See the post for more info.
in kotlin to start activity and pass some data you could try something like this:
startActivity(intentFor<NameOfActivity>(STRING to data))
Have Fun
you have to add the type inside <>...its working for me
val people = intent.getParcelableExtra<People>(EXTRA_PEOPLE) as People
In your PeopleDetailActivity activity, initialize viewModel in onCreate
viewModel = this#PeopleDetailActivity.viewModel.apply { postPeopleId(getPeopleFromIntent().id) }
And also initialize this method :
private fun getPeopleFromIntent() = intent.getParcelableExtra<People>(peopleId) as People
Write below code for passing the intent extras :
companion object {
private const val objectId = "people"
fun startActivityModel(context: Context?, people: People) {
if (context != null) {
val intent = Intent(context, PeopleDetailActivity::class.java).apply {
putExtra(
objectId,
people
)
}
context.startActivity(intent)
}
}
}
Call the above method in your PeopleListViewHolder onClick
override fun onClick(v: View?) = PeopleDetailActivity.startActivityModel(context(), people)
Your People class needs to implement Parcelable like this:
class People(): Parcelable{
// stuff
}
android studio will show you an error. Simple move your cursor to "People" and hit alt+enter. It should now show the option to generate a Parcelable implementation.
This generated code will contain something like
parcel.writeString(member1)
parcel.writeInt(member2)
and somewhere else
member1=parcel.readString()
member2=parcel.readInt()
Make sure that all members are contained in these methods and when modifying it, make sure that read and write is happening in the exact same order.