Use of ClassLoader in parcelable.readArrayList() in Android - java

I've got a Class named as FilterData which implements Parcelable. I had a member variable private ArrayList<String> listPropertyType; When implementing the parcelable interface in my class, parcel.readArrayList(null) ,the parameter is shown as ClassLoader object. With this member variable the FilterData class works as intended. But I wanted to implement a scenario where ClassLoader object is passed to readArrayList() method.
So what I've gathered from the documentation which is unclear about the public ArrayList readArrayList (ClassLoader loader) that if the ArrayList contains non primitive class ,we have to use ClassLoader.
What's the use case in this scenerio of using a ClassLoader and how to use it? I wanted to use the ClassLoader in the following matter. Added a member variable private ArrayList<RandomClass> listRandom; RandomClass randomClass; to implement this.
My FilterData class holds :
public FilterData(Parcel parcel)
{
listPropertyType=parcel.readArrayList(null);
listRandom=parcel.readArrayList(randomClass);
}
#Override
public void writeToParcel(Parcel parcel, int i) {
parcel.writeList(listPropertyType);
parcel.writeList(listRandom);
}
And my RandomClass is :
public class RandomClass extends ClassLoader{
//this class is for testing classloader in ArrayList in parcelable .readArrayList()
String name;
int age;
public RandomClass(String name, int age) {
this.name = name;
this.age = age;
}
}
This implementation doesn't work. So how to use this?

First of all, if we want to get a class Parcelable, we can use a website www.parcelabler.com. It will generate the syntax of the class. Secondly If we wanted to use an ArrayList of a class of our own. Making a Parcelable class containing the ArrayList won't work. We need to make the custom class Parcelable as well.
If a class that needs to be parcelable contains a member variable ArrayList<String> list; , the class which is generated from the website, should be :
public class MyParcelable implements Parcelable {
ArrayList<String> list;
public MyParcelable(ArrayList<String> list) {
this.list = list;
}
protected MyParcelable(Parcel in) {
if (in.readByte() == 0x01) {
list = new ArrayList<String>();
in.readList(list, String.class.getClassLoader());
} else {
list = null;
}
}
#Override
public int describeContents() {
return 0;
}
#Override
public void writeToParcel(Parcel dest, int flags) {
if (list == null) {
dest.writeByte((byte) (0x00));
} else {
dest.writeByte((byte) (0x01));
dest.writeList(list);
}
}
#SuppressWarnings("unused")
public static final Parcelable.Creator<MyParcelable> CREATOR = new Parcelable.Creator<MyParcelable>() {
#Override
public MyParcelable createFromParcel(Parcel in) {
return new MyParcelable(in);
}
#Override
public MyParcelable[] newArray(int size) {
return new MyParcelable[size];
}
};
}
And if the Class contains an ArrayList of a class made by us like this ArrayList<DummyClass> list; , the code should be:
public class MyComplexParcelable implements Parcelable {
ArrayList<DummyClass> list;
public MyComplexParcelable(ArrayList<DummyClass> list) {
this.list = list;
}
#Override
public String toString() {
return "MyComplexParcelable{" +
"list=" + list +
'}';
}
protected MyComplexParcelable(Parcel in) {
if (in.readByte() == 0x01) {
list = new ArrayList<DummyClass>();
in.readList(list, DummyClass.class.getClassLoader());
} else {
list = null;
}
}
#Override
public int describeContents() {
return 0;
}
#Override
public void writeToParcel(Parcel dest, int flags) {
if (list == null) {
dest.writeByte((byte) (0x00));
} else {
dest.writeByte((byte) (0x01));
dest.writeList(list);
}
}
#SuppressWarnings("unused")
public static final Parcelable.Creator<MyComplexParcelable> CREATOR = new Parcelable.Creator<MyComplexParcelable>() {
#Override
public MyComplexParcelable createFromParcel(Parcel in) {
return new MyComplexParcelable(in);
}
#Override
public MyComplexParcelable[] newArray(int size) {
return new MyComplexParcelable[size];
}
};
}
If we compare both cases , there is no difference in the syntax. Both class uses in.readList(list, String.class.getClassLoader()); and in.readList(list, DummyClass.class.getClassLoader()); which uses ClassLoader object of the correspondent classes.
But in the latter case, we need to make the DummyClass parcelable. Like this:
public class DummyClass implements Parcelable {
int age;
String name;
public DummyClass(int age, String name) {
this.age = age;
this.name = name;
}
#Override
public String toString() {
return "DummyClass{" +
"age=" + age +
", name='" + name + '\'' +
'}';
}
protected DummyClass(Parcel in) {
age = in.readInt();
name = in.readString();
}
#Override
public int describeContents() {
return 0;
}
#Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(age);
dest.writeString(name);
}
#SuppressWarnings("unused")
public static final Parcelable.Creator<DummyClass> CREATOR = new Parcelable.Creator<DummyClass>() {
#Override
public DummyClass createFromParcel(Parcel in) {
return new DummyClass(in);
}
#Override
public DummyClass[] newArray(int size) {
return new DummyClass[size];
}
};
}

If the array list you're reading from the parcel is subclass you've made, and not a standard java list subclass, or if it contains non-primitive types, you need to pass MyCustomList.class.getClassLoader().
You need to do it this way because parcelables can be inflated everywhere, anytime, so the code must know the ClassLoader required to load it. Most of the time you won't need it because you will be parcelling stuff inside your own app, but it is a good practice.
In fact, you said the list is a simple ArrayList<String>, so you don't need to do anything. It would be different if it was a MyListSubclass<String>, a ArrayList<MyObject>, or an ArrayList<HashMap<Object, Object>> that may contain other subclassed objects. You get the idea.

Related

How to pass a Parcelable class which contains a list of another parcelable class?

I'm trying to pass a Parcelable class from one Activity to another. I do it like this:
Intent intent = new Intent(ClosedChatActivity.this, AdminProfileActivity.class);intent.putExtra("adminProfile", adminProfile);
startActivity(intent);
And then get it in the other Activity like this:
adminProfile = (AdminProfile) getIntent().getExtras().getParcelable("adminProfile");
This is the AdminProfile class and the WebLink class it has inside:
public class AdminProfile implements Parcelable {
public static final Creator<AdminProfile> CREATOR = new Creator<AdminProfile>() {
#Override
public AdminProfile createFromParcel(Parcel in) {
return new AdminProfile(in);
}
#Override
public AdminProfile[] newArray(int size) {
return new AdminProfile[size];
}
};
public Long idUser;
public String name;
public String professio;
public String description;
public List<WebLink> webLinks;
public Long idOficina;
protected AdminProfile(Parcel in) {
idUser = in.readLong();
name = in.readString();
professio = in.readString();
description = in.readString();
webLinks = in.createTypedArrayList(WebLink.CREATOR);
idOficina = in.readLong();
}
#Override
public int describeContents() {
return 0;
}
#Override
public void writeToParcel(Parcel parcel, int i) {
parcel.writeLong(idUser);
parcel.writeString(name);
parcel.writeString(professio);
parcel.writeString(description);
parcel.writeLong(idOficina);
parcel.writeTypedList(webLinks);
}
}
public class WebLink implements Parcelable {
public static final Creator<WebLink> CREATOR = new Creator<WebLink>() {
#Override
public WebLink createFromParcel(Parcel in) {
return new WebLink(in);
}
#Override
public WebLink[] newArray(int size) {
return new WebLink[size];
}
};
public String name;
public String url;
protected WebLink(Parcel in) {
name = in.readString();
url = in.readString();
}
#Override
public boolean equals(#Nullable Object obj) {
WebLink webLink = (WebLink) obj;
assert webLink != null;
return this.name.equals(webLink.name) && this.url.equals(webLink.url);
}
#Override
public int describeContents() {
return 0;
}
#Override
public void writeToParcel(Parcel parcel, int i) {
parcel.writeString(name);
parcel.writeString(url);
}
}
When I get the object, the two Strings in it aren't the same I sent. Why is that?
The weird thing is that I have another Parcelable class which contains an AdminProfile, and when sending that class it does send fine with the Weblinks included, but if I send only an AdminProfile somewhere in the way it fails.
When you read and write Parcelable classes, you need to make sure that the order of the elements is exactly the same.
You are writing idOficina followed by WebLinks, but you are reading them in the opposite order.

How to pass objects within objects between activities with parcelable

Im pretty new in Android Studio.
I'm trying to pass an ArrayList from one activity to another using parcelable. Within the class Recipe I declare another ArrayList which I cannot get a hold of when starting the other activity.
Recipe.java:
public class Recipe implements Parcelable {
String name;
ArrayList<Ingredient> ingredients;
public Recipe(String name){
this.name = name;
this.ingredients = new ArrayList<>();
}
protected Recipe(Parcel in) {
name = in.readString();
}
public static final Creator<Recipe> CREATOR = new Creator<Recipe>() {
#Override
public Recipe createFromParcel(Parcel in) {
return new Recipe(in);
}
#Override
public Recipe[] newArray(int size) {
return new Recipe[size];
}
};
public void addIngredients(String[] amountList, String[] ingredientList, String[] unitList) {
for (int i = 0; i < ingredientList.length; i++) {
ingredients.add(new Ingredient(ingredientList[i], Double.parseDouble(amountList[i]), unitList[i]));
}
}
#Override
public int describeContents() {
return 0;
}
#Override
public void writeToParcel(Parcel parcel, int i) {
parcel.writeString(name);
}
}
Ingredient.java:
public class Ingredient implements Parcelable {
private String ingrdnt;
private double amount;
private String unit;
private String cat;
private boolean checkedItem;
public Ingredient(String ingrdnt, double amount, String unit) {
this.ingrdnt = ingrdnt;
this.amount = amount;
this.unit = unit;
//this.cat = category;
this.checkedItem = false;
}
protected Ingredient(Parcel in) {
ingrdnt = in.readString();
amount = in.readDouble();
unit = in.readString();
cat = in.readString();
checkedItem = in.readByte() != 0;
}
public static final Creator<Ingredient> CREATOR = new Creator<Ingredient>() {
#Override
public Ingredient createFromParcel(Parcel in) {
return new Ingredient(in);
}
#Override
public Ingredient[] newArray(int size) {
return new Ingredient[size];
}
};
public double getAmount() {
return amount;
}
#Override
public int describeContents() {
return 0;
}
#Override
public void writeToParcel(Parcel parcel, int i) {
parcel.writeString(ingrdnt);
parcel.writeDouble(amount);
parcel.writeString(unit);
parcel.writeString(cat);
parcel.writeByte((byte) (checkedItem ? 1 : 0));
}
}
In main:
private ArrayList<Recipe> recipes = new ArrayList<>();
//recipes obviously holds a bunch of recipes so it's not empty.
intent.putExtra("recipes", recipes);
System.out.println(recipes.get(0).ingredients.get(0).getAmount());
System.out: 2.0
In second activity:
recipes = this.getIntent().getParcelableArrayListExtra("recipes");
//Same print as above
System.out.println(recipes.get(0).ingredients.get(0).getAmount());
Caused by: java.lang.NullPointerException: Attempt to invoke virtual method 'int java.util.ArrayList.size()' on a null object reference
Have I implemented the parcelable in a wrong way or why can I not get a hold of the Ingredient objects?
I've read about other ways to pass objects between activities but it seems like parcelable might be the best way to do it.
Yes, you basically forgot to write the ingredients of the Recipe to the output Parcel which is given to the Recipe.writeToParcel method.
You can write the ArrayList<Parcelable> with writeTypedList and read it back with readTypedList.
So your Recipe constructor which accepts a Parcel should be like:
protected Recipe(Parcel in) {
name = in.readString();
ingredients = new ArrayList<>();
in.readTypedList(ingredients, Ingredient.CREATOR);
}
while your writeToParcel of the Recipe should become:
#Override
public void writeToParcel(Parcel parcel, int i) {
parcel.writeString(name);
parcel.writeTypedList(ingredients);
}
The NullPointerException you are seeing is caused by the fact that you do not allocate a new ArrayList in the constructor of Recipe which accepts a Parcel, so when you call recipes.get(0).ingredients.get(0).getAmount() in the second Activity the ingredients ArrayList is null, thus the Exception.
Also note (but not related to the problem) that there exist a writeBoolean and a readBoolean with which you can write and read values of type boolean (I am saying this for the Ingredient class implementation).
Try those out and let us know if it worked properly.

Android abstract parcelable class

Problem Description
I have Category class which implements Parcelable, also I have few more classes which are extended from the Category class. My base class has 2 protected members title and id which are set mainly from the inherited classes. So in order not to implement Parcelable related stuff everywhere in the inherited classes I decide to do it in a base class and let it handle all actions.
Question
The problem is that I can't have constructor of Category class as it is abstract class. So what is the solution? As I have abstract methods in the class I can't remove abstract modifier.
Source code
public abstract class Category implements Parcelable {
private static Map<Integer, Category> categoryMap = new TreeMap<Integer, Category>();
protected Sting title;
protected Integer id;
static {
categoryMap.put(0, new Taxi());
categoryMap.put(1, new Hotel());
}
private Category(Parcel in) {
this.id = in.readInt();
this.title = in.readString();
}
#Override
public int describeContents() {
return 0;
}
#Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInteger(id);
dest.writeString(title);
}
public static final Parcelable.Creator<Category > CREATOR = new Parcelable.Creator<Category >() {
public Category createFromParcel(Parcel in) {
return new Category (in); <=== !!! THIS IS NOT ALLOWED AS CLASS IS ABSTRACT !!!
}
public Category [] newArray(int size) {
return new Category[size];
}
};
abstract void generateCodes();
abstract String getImageIcon();
};
public final class Taxi extends Category {
public Taxi() {
title = "taxi";
id = 1547845;
}
};
public final class Hotel extends Category {
public Hotel() {
title = "hotel";
id = 1397866;
}
};
It is possible to have constructors in abstract classes, and you can do the parceling there - just make sure you call the respective super(...) method in the child classes.
Check this out.
EDIT:
I think Category does not need to implement CREATOR as you cannot instantiate it...? Similar suggestions are here and here
public abstract class Category implements Parcelable {
private static Map<Integer, Category> categoryMap = new TreeMap<Integer, Category>();
protected String title;
protected Integer id;
static {
categoryMap.put(0, new Taxi());
categoryMap.put(1, new Hotel());
}
protected Category(){}
protected Category(Parcel in) {
this.id = in.readInt();
this.title = in.readString();
}
#Override
public int describeContents() {
return 0;
}
#Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInteger(id);
dest.writeString(title);
}
abstract void generateCodes();
abstract String getImageIcon();
};
public final class Taxi extends Category {
public Taxi() {
title = "taxi";
id = 1547845;
}
protected Taxi(Parcel in) {
super(in);
}
public static final Parcelable.Creator<Taxi> CREATOR = new Parcelable.Creator<Taxi>() {
public Category createFromParcel(Parcel in) {
return new Taxi (in);
}
public Category [] newArray(int size) {
return new Taxi[size];
}
};
};
public final class Hotel extends Category {
public Hotel() {
title = "hotel";
id = 1397866;
}
protected Hotel(Parcel in) {
super(in);
}
public static final Parcelable.Creator<Hotel> CREATOR = new Parcelable.Creator<Hotel>() {
public Category createFromParcel(Parcel in) {
return new Hotel (in);
}
public Category [] newArray(int size) {
return new Hotel[size];
}
};
};

Abstract class as parcelable

Basicly I have the following structure in my app:
It would be straightforward to implement such a structure without the abstract class ProjectItem, but in this case I don't know how to implement this.
The abstract class ProjectItem needs a CREATOR as it should be parcelable. (like
in.readTypedList(mProjectItems, ProjectItem.CREATOR); within the constructor Project(Parcel in))
But in fact, the CREATOR can only be implemented in its derived classes for logical reasons.
So, how to implement this structure in order to keep the class Project parcelable??
Edit
This is what one of the constructors of Project looks like:
private Project(Parcel in) {
in.readTypedList(mProjectItems, ProjectItem.CREATOR);
}
But as I already said, ProjectItem shouldn't have to implement a CREATOR
My solution is similar to evertvandenbruel's. But I identify the concrete class using an int so that I can use a switch block. I also have that switch block in a static getConcreteClass(Parcel) method.
AbstractClass.java
public abstract class AbstractClass implements Parcelable {
public static final int CLASS_TYPE_ONE = 1;
public static final int CLASS_TYPE_TWO = 2;
public static final Creator<AbstractClass> CREATOR = new Creator<AbstractClass>() {
#Override
public AbstractClass createFromParcel(Parcel source) {
return AbstractClass.getConcreteClass(source);
}
#Override
public AbstractClass[] newArray(int size) {
return new AbstractClass[size];
}
};
protected String mAbstractClassString;
public AbstractClass(String abstractClassString) {
mAbstractClassString = abstractClassString;
}
public AbstractClass(Parcel source) {
mAbstractClassString = source.readString();
}
public static AbstractClass getConcreteClass(Parcel source) {
switch (source.readInt()) {
case CLASS_TYPE_ONE:
return new ConcreteClassOne(source);
case CLASS_TYPE_TWO:
return new ConcreteClassTwo(source);
default:
return null;
}
}
#Override
public int describeContents() {
return 0;
}
#Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(mAbstractClassString);
}
#Override
public String toString() {
return "Parent String: " + mAbstractClassString + '\n';
}
}
ConcreteClassOne.java
public class ConcreteClassOne extends AbstractClass {
private String mString;
public ConcreteClassOne(String abstractClassMemberString, String string) {
super(abstractClassMemberString);
mString = string;
}
public ConcreteClassOne(Parcel source) {
super(source);
mString = source.readString();
}
#Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(CLASS_TYPE_ONE);
super.writeToParcel(dest, flags);
dest.writeString(mString);
}
#Override
public String toString() {
return super.toString().concat("Child String: " + mString);
}
}
ConcreteClassTwo.java
public class ConcreteClassTwo extends AbstractClass {
private String mString;
private int mInt;
public ConcreteClassTwo(String abstractClassString, String string, int anInt) {
super(abstractClassString);
mString = string;
mInt = anInt;
}
public ConcreteClassTwo(Parcel source) {
super(source);
mString = source.readString();
mInt = source.readInt();
}
#Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(CLASS_TYPE_TWO);
super.writeToParcel(dest, flags);
dest.writeString(mString);
dest.writeInt(mInt);
}
#Override
public String toString() {
String string = super.toString();
for (int i = 0; i < mInt; i++) {
string = string.concat("Child String: " + mString + '\n');
}
return string;
}
}
The selected answer (from evertvandenbruel's post) has a bug in it. The correct code must account for parceling when just one of the subclasses is being parceled, not just a list of the superclass objects.
All the other code should be the same, the key is that you MUST read in the type variable in ALL creators (see code below). Otherwise there will be issues with the ordering when trying to unparcel a subclass object
Ex:
package com.example.parcelable_example.model;
import android.os.Parcel;
import android.os.Parcelable;
public class Cat extends Animal{
public Cat(String name){
super(name, "Cat");
}
public int describeContents() {
return 0;
}
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(getType());
super.writeToParcel(dest, flags);
}
public Cat(Parcel source) {
super(source);
}
public static final Parcelable.Creator<Cat> CREATOR = new Parcelable.Creator<Cat>() {
public Cat createFromParcel(Parcel in) {
/** DO NOT FORGET THIS!!! **/
type = in.readString();
return new Cat(in);
}
public Cat[] newArray(int size) {
return new Cat[size];
}
};
}
This question arises from a false assumption.
Here is a quote from the original post.
The abstract class ProjectItem needs a CREATOR as it should be
parcelable.
In fact, It is not necessary for the super class to define CREATOR since it is abstract.
Here is a minimal example which demonstrates the method.
/* Super class */
abstract class SuperClass
implements Parcelable {
protected SuperClass(Parcel in) {
mSuperId = in.readLong();
}
#Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeLong(mSuperId);
}
}
/* Sub class */
public class SubClass
extends SuperClass {
protected SubClass(Parcel in) {
super(in);
mSubId = in.readLong();
}
#Override
public void writeToParcel(Parcel dest, int flags) {
super.writeToParcel(dest, flags);
dest.writeLong(mSubId);
}
#Override
public int describeContents() {
return 0;
}
public static final Creator<SubClass> CREATOR = new Creator<SubClass>() {
#Override
public SubClass createFromParcel(Parcel in) {
return new SubClass(in);
}
#Override
public SubClass[] newArray(int size) {
return new SubClass[size];
}
};
}
/* Usage */
class AnotherClass {
void aMethod() {
Bundle args = new Bundle();
args.putParcelable("EXTRA_SUPER_CLASS", subClassObject);
}
}
public abstract class A implements Parcelable {
private int a;
protected A(int a) {
this.a = a;
}
public void writeToParcel(Parcel out, int flags) {
out.writeInt(a);
}
protected A(Parcel in) {
a = in.readInt();
}
}
public class B extends A {
private int b;
public B(int a, int b) {
super(a);
this.b = b;
}
public static final Parcelable.Creator<B> CREATOR = new Parcelable.Creator<B>() {
public B createFromParcel(Parcel in) {
return new B(in);
}
public B[] newArray(int size) {
return new B[size];
}
};
public int describeContents() {
return 0;
}
}

putParcelableArrayListExtra and black screen

I have custom class that implements Parcelable and I use it as custom arraylist.
When I use putParcelableArrayListExtra and 400 rows it works fine, but 1000 rows it does not. I have black screen and app locks up. What is wrong?
EDIT:
I sent it here and I don't use it in another Activity.
Intent intent = new Intent().setClass(getApplicationContext(), ArtActivity.class);
intent.putParcelableArrayListExtra ("mylist", list);
startActivityForResult(intent, SECONDARY_ACTIVITY_REQUEST_CODE);
My array:
ArrayList<Piece> list = new ArrayList<Piece>();
It is my Class:
public class Piece implements Parcelable {
private String id;
private String name;
private int type;
private String text;
private String mp3;
public Piece (String id,String name,int type)
{
this.id=id;
this.name=name;
this.type=type;
}
public Piece(Piece ele)
{
this.id=ele.id;
this.name=ele.name;
this.type=ele.type;
this.text=ele.text;
}
public Piece (Parcel in)
{
id = in.readString ();
name = in.readString ();
type = in.readInt();
text= in.readString();
mp3=in.readString();
}
public static final Parcelable.Creator<Piece> CREATOR
= new Parcelable.Creator<Piece>()
{
public Piece createFromParcel(Parcel in)
{
return new Piece(in);
}
public Piece[] newArray (int size)
{
return new Piece[size];
}
};
public void makeText(String text)
{
this.text=text;
}
public void makeMp3(String mp3)
{
this.mp3= mp3;
}
public String getMp3()
{
return this.mp3;
}
public String getId()
{
return id;
}
public String getName()
{
return name;
}
public int getType()
{
return type;
}
public String getText()
{
return text;
}
public int describeContents() {
// TODO Auto-generated method stub
return 0;
}
public void writeToParcel(Parcel dest, int flags) {
// TODO Auto-generated method stub
dest.writeString (id);
dest.writeString (name);
dest.writeInt(type);
dest.writeString (text);
dest.writeString (mp3);
}
}
I do not believe you should be using parcelable in this case. I would either access the data statically (if you only intend to have one persistent instance of the data), or use a caching system to hold onto the data.
This is an example of a publicly available static variable:
public static List<Piece> list;
It is accessible from everywhere in your app that has visibility of the class.
However, doing this is very messy and is considered a bad practice. Alternatively, you can create an object to manage the data for you as a static class or singleton:
public class MyListManager {
private static List<Piece> mList;
public static List<Piece> getMyList() {
return mList;
}
public static void setList(List<Piece> list) {
mList = list;
}
}
Alternatively, you can implement some kind of a caching system to manage your data.

Categories