How to pass data class as Parcelable in a bundle? - android

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)

Related

Passing array of objects from activity to service via intent

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

List of parcelable objects to parcelable Kotlin

I have a parcelable team class
#Parcelize
class Team(var name: String, var teamMembers: List<String>, var id: UUID): Parcelable
I have a service that returns a list of (currently hardcoded) Teams:
#Module
class TeamInfoModule #Inject constructor(): ITeamInfoModule {
#Provides
override fun getAllTeamData(): List<Team> { ... }
}
I want to be able to pass this list of teams into a Fragment from an activity like so:
class MainActivity: AppCompatActivity() {
#Inject
lateinit var teamInfoModule: TeamInfoModule;
lateinit var team: Team;
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
DaggerServiceModuleComponent.create().inject(this)
val bundle = Bundle()
val teamArrayList: List<Team> = this.teamInfoModule.getAllTeamData()
val homeFragment = HomeFragment()
bundle.putParcelable("teamData", teamArrayList)
homeFragment.arguments = bundle
}
}
This throws an error of: Type Mismatch. Required: Parcelable? Found: List<Team>.
I know that that a single team can be passed to my Fragment as it doesn't throw an error.
My question is, is there a utility that I haven't found that can somehow serialize a List to a Parcelable? I had the idea of creating a custom TeamListClass that also implements #Parcelize but I wanted to ask here before I went off and wrote code that I didn't need. Maybe something similar to a JS' Array.map() that will pass each Parcelable into the bundle?
You should use:
bundle.putParcelableArrayList("teamData", ArrayList(teamArrayList))
Convert the list to arrayList using ArrayList(teamArrayList)
bundle.putParcelableArrayList("teamData", ArrayList(teamArrayList))

kotlinx.serialization - Serialize ArrayList<Date> as data class variable with custom DateSerializer

I need to serialize ArrayList as data class variable with custom DateSerializer,
with single date variable i use annotation:
#Serializable
data class SearchBundle(
#Serializable(with = DateSerializer::class) var startDate: Date? = null)
Somebody know how to do it for arrayList of dates?
Probably easier then your current approach is to just specify the DateSerializer on file level of your SearchBundle-class via UseSerializers-annotation, e.g.:
#file:UseSerializers(DateSerializer::class)
import kotlinx.serialization.*
import java.util.*
#Serializable
data class SearchBundle(
var startDate: List<Date>? = null)
That way you can keep your DateSerializer as is and the rest of your code just works, i.e. it will automatically use DateSerializer for all Date-types within that file.
#Serializable
class TestDates(
#Optional #Serializable(with = DatesArraySerializer::class) var dates: ArrayList<Date>? = null
)
object DatesArraySerializer : KSerializer<ArrayList<Date>> {
override val descriptor = ArrayClassDesc(DateSerializer.descriptor)
override fun serialize(encoder: Encoder, obj: ArrayList<Date>) {
encoder.encodeSerializableValue(ArrayListSerializer(DateSerializer), obj)
}
override fun deserialize(decoder: Decoder): ArrayList<Date> {
val dates = decoder.decodeSerializableValue(ArrayListSerializer(DateSerializer))
return dates.toList() as ArrayList<Date>
}
}

pass data class between two fragments

I think I'm doing it wrong but this is my situation.
I'm getting json data inside a fragment then process it with Gson to a data class and display it. What I need is to use this data again inside another fragment in a custom spinner adapter which is ready.
As much as I understand it's impossible to pass objects so how can I do this !?
I have tried to use bundle and it didn't work
the onResponse method (First fragment)
override fun onResponse(call: Call?, response: Response?) {
val jsonString = response?.body()?.string()
val gson = GsonBuilder().create()
val data = gson.fromJson(jsonString,currancyModel::class.java)
countryData = data
activity?.runOnUiThread {
rootView.recyclerView.adapter = CountryAdapter(data)
}
}
the data class
data class currancyModel(val items: List<Item>)
data class Item(val countray :String,val rate:String)
the getView in the custom spinner adapter inside the second fragment
(I need my data here)
override fun getView(p0: Int, p1: View?, p2: ViewGroup?): View {
val view = inflater.inflate(R.layout.custome_spinner,null)
val img = view.findViewById<View>(R.id.spinner_image) as ImageView
val countary_name = view.findViewById<View>(R.id.spinner_country) as TextView
img.setImageResource(R.drawable.us)
countary_name.setText(country!![p0].countray)
return view
}
Do you display two fragments in your Activity simultaneously? If so, you can pass the data through it. Or implement some interface/observable/livedata to pass the data between the fragments.
If Fragment A fetches the data and then you change Fragment A to Fragment B, make your data classes Parcelable and then pass it as arguments when creating Fragment B:
companion object {
fun newInstance(yourData : DataClass) = FragmentB().apply { arguments = Bundle().apply { putParcelable("dataKey",yourData) }
}
Note: you can annotate your data class with #Parcelize. Compiler will then generate all Parcelable methods and factory class for you.
After you passed the data to Fragment B on creation, retrieve it with, for example:
val data: YourData by lazy { arguments?.getParcelable("dataKey") as YourData }
Indeed it is possible to pass objects from one fragment to another given your object class should implement Parcelable. And passing the object through the bundle by calling putParcelable on the bundle object.
1. class CurrancyModel : Parcelable {
//Implement Parcelable
}
And pass it between the fragments via Bundle.
2.var fragment = YourFragment().apply {
arguments = Bundle().apply{ putParcelable("dataKey",yourData) }
}
An idea could be to use a singleton data class, with a HashMap<String, Object> having a key with some kind of ID you create yourself, and then the value being the object you want to be able to retrieve. So in the onResponse you will add your data to the HashMap in the dataclass and then just retrieve it from the other class.

How to Pass custom object via intent in kotlin

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.

Categories

Resources