Issues to pass objects as parameters from activity to fragment - android

I am trying to pass an object as a parameter from an activity to a fragment.
I already searched over hill and dale to find out how to do it, I tried many methods but none of them worked.
Here is what I tried :
// In my Activity, where I call my fragment
var bundle = Bundle()
bundle.putParcelable("restaurant", restaurant)
var fragment = Home2Fragment()
fragment.arguments = bundle
supportFragmentManager.beginTransaction().replace(R.id.content, fragment, FRAGMENT_HOME).commit()
My IDE (Android Studio) underline "restaurant" in bundle.putParcelable line, telling me a type mismatch.
This is the same with the getArguments() methods, I get a type mismatch.
My class look like this :
#Parcel
class Establishment {
var title = ""
// More variables
fun loadFromJson(json: JsonObject) {
title = readString(json, TAG_TITLE)
// More API loads
}
companion object {
private const val TAG_TITLE = "title"
// More tags
}
}
I don't understand why my class is set as "Parcel" but is not recognized as a "Parcelable".
I am looking for an efficient way to pass parameters from an activity to a fragment.
Thanks a lot in advance.
EDIT :
As asked, I tried to make it as Serializable.
I can't make it Serializable, as "Serializable" does not exists.
But I tried to make it "SerializedName", but "This annotation is not applicable to target 'class' "

Your class should implement Parcelable this way:
class Establishment(val title: String) : Parcelable {
private constructor(p: Parcel) : this(
title = p.readString(),
override fun writeToParcel(dest: Parcel, flags: Int) {
dest.writeString(title)
}
override fun describeContents() = 0
companion object {
#JvmField val CREATOR = object : Parcelable.Creator<Establishment> {
override fun createFromParcel(parcel: Parcel) = Establishment(parcel)
override fun newArray(size: Int) = arrayOfNulls<Establishment>(size)
}
}
fun loadFromJson(json: JsonObject) {
title = readString(json, TAG_TITLE)
// More API loads
}
}

So with your help I finally found out how to do this.
In my fragment, I modified my newInstance() fun :
companion object {
fun newInstance(restaurant : Establishment) : Home2Fragment {
val fragment = Home2Fragment()
val args = Bundle()
args.putParcelable("restaurant", restaurant)
fragment.arguments = args
return (fragment)
}
}
This was not very difficult, it was before as my IDE was telling me that my object "Establishment" was not a Parcelable Object.
So the real problem was to set "Establishment" as a Parcelable Object.
It gave me hard time so I decided to use Parcelable Generator plugin, as adviced by TpoM6oH.
My class Establishment now look like this :
#Parcel
class Establishment() : Parcelable {
var title = ""
// More var
fun CREATOR(): Parcelable.Creator<Establishment> = object : Parcelable.Creator<Establishment> {
override fun createFromParcel(p0: android.os.Parcel?): Establishment {
return Establishment()
}
override fun newArray(p0: Int): Array<Establishment> {
return null as Array<Establishment>
}
}
constructor(parcel: android.os.Parcel) : this() {
this = parcel.readString()
// More var
}
fun loadFromJson(json: JsonObject) {
title = readString(json, TAG_TITLE)
// More load from API
}
companion object {
private const val TAG_TITLE = "title"
}
override fun writeToParcel(parcel: android.os.Parcel, flags: Int) {
parcel.writeString(title)
// More var
}
override fun describeContents() : Int {
return 0
}
}
The plugin Parcelable Generator added some mysterious functions in my class, and "Establishment" (line 2) is still a problem in my IDE opinion ("This class implements Parcelable but does not provide a CREATOR field").
But the code DOES compile ! And it is working !
Thanks everyone for your advice.

Related

how to solve error ArrayList cannot be cast to android.os.Parcelable[] in Kotlin when passing data using navigation argument?

I am trying to send categories data (ArrayList) from a fragment to another fragment using navigation argument like this.
fun setUpArgumentForSearchDestination(categories: ArrayList<Category>) {
val searchDestination = fragmentView.findNavController().graph.findNode(R.id.destination_search)
searchDestination?.addArgument(
"allCategories", NavArgument.Builder()
.setType(NavType.ParcelableArrayType(Category::class.java))
.setDefaultValue(categories)
.build())
}
and in the search fragment, I receive the data from argument like this:
arguments?.let {
// I get error in the code below:
val categories = it.getParcelableArrayList<Category>("allCategories")
}
and I get error message:
java.util.ArrayList cannot be cast to android.os.Parcelable[]
I have tried to find the answer even though I am not sure the cause of this problem, it seems that I have to custom my Category class like in this thread: Read & writing arrays of Parcelable objects
but I am a beginner, and not really understand with that answer from that thread. I have tried to implement parcelable, but it still doesn't work. here is my Category class
class Category() : Parcelable {
var id : Int = 0
var name : String = ""
var parentID : Int = 0
var imageURL : String = ""
constructor(parcel: Parcel) : this() {
id = parcel.readInt()
name = parcel.readString()
parentID = parcel.readInt()
imageURL = parcel.readString()
}
override fun writeToParcel(parcel: Parcel, flags: Int) {
parcel.apply {
writeInt(id)
writeString(name)
writeInt(parentID)
writeString(imageURL)
}
}
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)
}
}
}
A ParcelableArrayType only supports arrays of Parcelable objects, not lists. Therefore you must convert your ArrayList into an array using toTypedArray():
val searchDestination = fragmentView.findNavController().graph.findNode(R.id.destination_search)
searchDestination?.addArgument(
"allCategories", NavArgument.Builder()
.setType(NavType.ParcelableArrayType(Category::class.java))
.setDefaultValue(categories.toTypedArray())
.build())
}
You'd retrieve your array of Parcelables using Safe Args or code such as:
arguments?.let {
val categories = it.getParcelableArray("allCategories") as Array<Category>
}
Navigation does not support ArrayLists of Parcelables.

"Parcelable protocol requires a Parcelable.Creator object called CREATOR" (I do have CREATOR) - in Kotlin

I get the Error message "Parcelable protocol requires a Parcelable.Creator object called CREATOR on class .....", but I do have a Creator and I don't know what is wrong with it. I copied it from https://developer.android.com/reference/android/os/Parcelable and changed the class name to fit my code.
I suspect that the (automatic) conversion from Java to Kotlin wasn't perfect (or to be precise: did it slightly different than it would be needed), but I don't know what the problem is exactly.
There is a Thread with the same Error Message (Parcelable protocol requires a Parcelable.Creator object called CREATOR (I do have CREATOR)) but the problem there was that
"fun writeToParcel" did not write in in the same order as it is read in "fun DataHandler". That is not the Problem in my case as it's in the same order.
Another answer there pointed out that it could be a problem that the function needs to be static. However, Kotlin has no "static" function. I read that it's done with "Companion object". I tried that (see below), but it threw another error - and I'm not sure if it would even work.
class DataHandler : Parcelable {
var player1name = ""
var player1color = 0
//main constructor
fun DataHandler(player1name: String, player1color: Int) {
this.player1name = player1name
this.player1color = player1color
}
//write object values to parcel for storage
override fun writeToParcel(dest: Parcel, flags: Int) {
//write all properties to the parcle
dest.writeString(player1name)
dest.writeInt(player1color)
}
//constructor used for parcel
fun DataHandler(parcel: Parcel) {
//read and set saved values from parcel
player1name = parcel.readString()
player1color = parcel.readInt()
}
//creator - used when un-parceling our parcle (creating the object)
val CREATOR: Parcelable.Creator<DataHandler> = object : Parcelable.Creator<DataHandler> {
override fun createFromParcel(parcel: Parcel): DataHandler {
return DataHandler(parcel) as DataHandler
}
override fun newArray(size: Int): Array<DataHandler?> {
return arrayOfNulls<DataHandler>(size)
}
}
//return hashcode of object
override fun describeContents(): Int {
return hashCode()
}
}
This is the sending activity:
val intentPickPlayer = Intent(this, PlayGame::class.java)
var dataHandler = DataHandler()
dataHandler.player1name = "testing"
intentPickPlayer.putExtra("data", dataHandler)
startActivity(intentPickPlayer)
This is the receiving activity:
class PlayGame : Activity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.playgame)
val test = intent.getParcelableExtra<DataHandler>("data")
Toast.makeText(this, test.player1name, Toast.LENGTH_SHORT).show()
}
Like said above: I tried to make the CREATOR static by putting it into a companion object (apparently that's the way it works in Kotlin, but that produces another error (and I'm not sure if it fixes the first problem)
companion object {
//creator - used when un-parceling our parcle (creating the object)
val CREATOR: Parcelable.Creator<DataHandler> = object : Parcelable.Creator<DataHandler> {
override fun createFromParcel(parcel: Parcel): DataHandler {
//HERE COMES AN ERROR: parcel has a red underlining and it says: "too many Arguments for public constructor DataHandler()
return DataHandler(parcel) as DataHandler
}
override fun newArray(size: Int): Array<DataHandler?> {
return arrayOfNulls<DataHandler>(size)
}
}
In Kotlin constructors are defined by the constructor keyword - https://kotlinlang.org/docs/reference/classes.html
See also https://kotlinlang.org/docs/reference/classes.html#secondary-constructors for info on secondary constructors. Secondary constructors need to delegate to the primary one.
Default constructors are usually defined after the class name and properties defined as part of it:
class DataHandler(var player1name: String, var player1color: Int) : Parcelable {
//write object values to parcel for storage
override fun writeToParcel(dest: Parcel, flags: Int) {
//write all properties to the parcle
dest.writeString(player1name)
dest.writeInt(player1color)
}
//constructor used for parcel
constructor(parcel: Parcel) : this(
//read and set saved values from parcel
player1name = parcel.readString(),
player1color = parcel.readInt())
companion object {
#JvmField
//creator - used when un-parceling our parcle (creating the object)
val CREATOR: Parcelable.Creator = object : Parcelable.Creator {
override fun createFromParcel(parcel: Parcel): DataHandler {
return DataHandler(parcel) as DataHandler
}
override fun newArray(size: Int): Array {
return arrayOfNulls(size)
}
}
}
//return hashcode of object
override fun describeContents(): Int {
return hashCode()
}
}

Android kotlin: putParcelableArrayList(ArrayList<int>) and getParcelableArrayList<Int>() wont work

I am trying to pass ArrayList<Integer> from fragment to another fragment, here is my code:
Kotlin code
companion object {
fun newInstance(categoryId: Int, brandsList: ArrayList<Int>): Fragment {
val fragment = CategoryAllAdsFragment()
fragment.arguments = Bundle()
fragment.arguments!!.putInt(Constant.CATEGORY_ID, categoryId)
fragment.arguments!!.putParcelableArrayList(Constant.BRANDS_LIST, brandsList)
return fragment
}
}
but it says:
Type mismatch.
Required: java.util.ArrayList !
Found: kotlin.collections.ArrayList /* = java.util.ArrayList */
The same thing when I was trying to read it.
Kotlin code
try {
val brandsList = arguments!!.getParcelableArrayList<Int>(Constant.BRANDS_LIST)
} catch (ex: Exception) {
throw Exception("brand list cannot be null")
}
It says:
Type argument is not within its bounds
Expected: Parcelable!
Found: Int
I've tested it with Java and its work fine.
You can make a general Parcelable class that contains a variable of type (Any)
class BaseParcelable : Parcelable {
var value: Any
constructor(value: Any) {
this.value = value
}
constructor(parcel: Parcel) {
this.value = Any()
}
override fun writeToParcel(dest: Parcel?, flags: Int) {}
override fun describeContents(): Int = 0
companion object CREATOR : Parcelable.Creator<BaseParcelable> {
override fun createFromParcel(parcel: Parcel): BaseParcelable {
return BaseParcelable(parcel)
}
override fun newArray(size: Int): Array<BaseParcelable?> {
return arrayOfNulls(size)
}
}
}
Then use this class to pass data between fragments or between (Activty to Fragment)
To pass list from fragment to fragment for example:
companion object {
fun newInstance(categoryId: Int, brandsList: ArrayList<Int>): Fragment {
val fragment = CategoryAllAdsFragment()
fragment.arguments = Bundle()
fragment.arguments!!.putInt(Constant.CATEGORY_ID, categoryId)
fragment.arguments!!.putParcelable(Constant.BRANDS_LIST, BaseParcelable(brandsList))
return fragment
}
}
To get the list:
val any = arguments?.getParcelable<BaseParcelable>(Constant.BRANDS_LIST).value
val list = any as ArrayList<Int>
Use
putIntegerArrayList(String key, ArrayList value)
Inserts an ArrayList value into the mapping of this Bundle, replacing any
existing value for the given key
putIntegerArrayList(Constant.BRANDS_LIST, array)
And get like
getIntegerArrayList(String key)
Returns the value associated with the given key, or null if no mapping
of the desired type exists for the given key or a null value is
explicitly associated with the key.
extras.getIntegerArrayList(Constant.BRANDS_LIST)

Get generics class loader for parsing nested Parcelable generic field

I have a wrapper of Parcelable generic type but Parcel constructing fails to compile because T class can not be determined generically
class MyItem<T : Parcelable> (val model: T) : Parcelable {
constructor(parcel: Parcel) :
this(parcel.readParcelable(T::class.java.classLoader)) {
}
}
Is there any solution to this case?
In order to get the whole picture here is what I ended up with:
The use case is one has a Parcelable generic instance let's call it model which should be completed with common properties of Parcelable wrapper in order to not pollute the model with extrinsic fields. For example Item wrapper.
In the example below the wrapper extra property gives us some type of index :
class Item<T : Parcelable> (val model: T, val index: Int ) : Parcelable {
constructor(parcel: Parcel) :
this(parcel.readParcelable(
Item<T>::model.javaClass.classLoader),
parcel.readInt()
) {}
override fun writeToParcel(parcel: Parcel?, flag: Int) {
parcel?.writeParcelable(model, 0)
parcel?.writeInt(index)
}
override fun describeContents(): Int {
return 0
}
companion object CREATOR : Parcelable.Creator<Item<Parcelable>> {
override fun createFromParcel(parcel: Parcel): Item<Parcelable> {
return Item(parcel)
}
override fun newArray(size: Int): Array<Item<Parcelable>?> {
return arrayOfNulls(size)
}
}
}
So in the end we can have something like: Item(Person("John"), 0), Item(Person("Bill"), 1)...
class PersonPagerFragment() : BasePagerFragment<Person>() {
companion object {
fun newInstance(itemList: ArrayList<Item<Person>>)
: PersonPagerFragment {
val args = Bundle()
val fragment = PersonPagerFragment()
args.putParcelableArrayList("items", itemList)
fragment.arguments = args
return fragment
}
}
}
extending class like:
class BasePagerFragment<T : Parcelable> : Fragment(){
protected fun readBundle(bundle: Bundle?) {
bundle.getParcelableArrayList<Item<T>>("items")
}
}
You can use a reified inline function as a factory method to achieve this. I prefer to do this on a companion object. Here's an MCVE:
class MyItem<T> (val model: T) {
companion object {
inline fun <reified T> from(parcel : T) : MyItem<T> {
return MyItem<T>(T::class.java.newInstance())
}
}
}
fun main(args: Array<String>) {
val mi = MyItem.from("hi")
println("mi is a ${mi::class}")
}
If you need to have a Parcel-type constructor, you could change that to be the primary constructor, and figure out the type of the MyItem then.
class Parcel
class MyItem(val parcel: Parcel) {
inline fun <reified T> model() : T {
// You code would be calling
// `parcel.readParcelable(T::class.java.classLoader)`
return T::class.java.newInstance() as T
}
}
fun main(args: Array<String>) {
// You don't ned to know the out type when constructing MyItem.
val mi = MyItem(Parcel())
// But you do need to know it when calling model()
val model : String = mi.model()
println("mi.model is a ${model::class}")
}

Kotlin getParcelableArray from intent bundle not able to cast it to custom type

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)
}
}

Categories

Resources