My class ExpenseFB, which implements Parcelable, contains a Map of UserFB (which implements Parcelable too):
ExpenseFB:
public class ExpenseFB implements Parcelable {
private String id;
private String name;
private String description;
private String whopaidID;
private String whopaidName;
private Double amount;
private Map<String, UserFB> partecipants;
// setters and getters...
#Override
public int describeContents() {
return 0;
}
#Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(id);
dest.writeString(name);
dest.writeString(description);
dest.writeString(whopaidID);
dest.writeString(whopaidName);
dest.writeMap(partecipants);
}
protected ExpenseFB(Parcel in) {
id = in.readString();
name = in.readString();
description = in.readString();
whopaidID = in.readString();
whopaidName = in.readString();
in.readMap(partecipants,UserFB.class.getClassLoader());
}
public static final Creator<ExpenseFB> CREATOR = new Creator<ExpenseFB>() {
#Override
public ExpenseFB createFromParcel(Parcel in) {
return new ExpenseFB(in);
}
#Override
public ExpenseFB[] newArray(int size) {
return new ExpenseFB[size];
}
};
}
UserFB:
public class UserFB implements Parcelable{
private String id;
private String name;
private String email;
private Map<String, GroupFB> groups;
private Map<String, UserFB> friends;
// setters and getters
#Override
public int describeContents() {
return 0;
}
#Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(id);
dest.writeString(name);
dest.writeString(email);
dest.writeMap(groups);
dest.writeMap(friends);
}
protected UserFB(Parcel in) {
id = in.readString();
name = in.readString();
email = in.readString();
in.readMap(groups,GroupFB.class.getClassLoader());
in.readMap(friends,UserFB.class.getClassLoader());
}
public static final Creator<UserFB> CREATOR = new Creator<UserFB>() {
#Override
public UserFB createFromParcel(Parcel in) {
return new UserFB(in);
}
#Override
public UserFB[] newArray(int size) {
return new UserFB[size];
}
};
}
I want to pass an ExpenseFB object between two Activities by adding the object
ExpenseFB to the intent:
intent.putExtra("id", expenseFB);
When, in debug mode, I execute getIntent().getParcelableExtra("id") in the second activity it raises the following exception when tries to do the readMap() method on the partecipants map:
... Caused by: java.lang.NullPointerException: Attempt to invoke interface method 'java.lang.Object java.util.Map.put(java.lang.Object, java.lang.Object)' on a null object reference
I see that the partecipants map in the first activity is filled: I think that the problem is in the writeMap() method.
Does exist a standard or better way to pass a Parcelable object containing a Map?
Have I to call another method to parcel the Map?
I don't want to use Serializable object because I read that they make worse performances.
The problem is that readMap() is used to read data from a Parcel into and existing Map. You haven't created the Map before calling readMap(), so you get the NullPointerException.
You can solve this by initializing the map when you declare it:
private Map<String, GroupFB> groups = new HashMap<String, GroupFB>();
private Map<String, UserFB> friends = new HashMap<String, UserFB>();
Or, you can create the empty Map in the UserFB constructor, like this:
protected UserFB(Parcel in) {
id = in.readString();
name = in.readString();
email = in.readString();
groups = new HashMap<String, GroupFB>();
in.readMap(groups,GroupFB.class.getClassLoader());
friends = new HashMap<String, UserFB>()
in.readMap(friends,UserFB.class.getClassLoader());
}
You got the point but I think you need to know how to write Map<> into parcelable
Pasting writeParcel() method
#Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(this.groups.size());
for (Map.Entry<String, GroupFb> entry : this.groups.entrySet()) {
dest.writeString(entry.getKey());
dest.writeParcelable(entry.getValue(), flags);
}
}
protected UserFB (Parcel in) {
int groupsSize = in.readInt();
this.groups = new HashMap<String, GroupFb>(groupsSize);
for (int i = 0; i < groupsSize; i++) {
String key = in.readString();
GroupFb value = in.readParcelable(GroupFb.class.getClassLoader());
this.groups.put(key, value);
}
}
Do the same for another Map<> too.
Related
I would like to send an array of objects between activities. I want to use the parcelable interface and send the data in an intent. However I keep getting errors. I have been stuck for 2 days. Here are some details about my problem.
Class A
private ProjetUI[] mProjects;
private final View.OnClickListener mOnClickListener = new View.OnClickListener() {
#Override
public void onClick(View view) {
Context context = view.getContext();
Intent intent = new Intent(context, ProjetListActivity.class);
intent.putExtra(ProjetListActivity.ARG_PROJECTS, mProjects);
context.startActivity(intent);
}
};
Class B
ProjetUI[] mProjects = getIntent().getParcelableArrayExtra(ARG_PROJECTS);
I get a compilation error "Incompatible types"
After casting to (ProjetUI[]), I get a runtime error "Cannot cast Parcelable[] to ProjetUI[]"
Class Projet
public class ProjetUI implements Parcelable {
private String id;
private String idParent;
private String nom;
private String description;
private List<ProjetColonneUI> colonnes;
private List<VueUI> vues;
private boolean archive;
private String version;
private String commentaire;
private boolean published;
private List<DroitAccesUI> droitAcces;
private String idDossier;
private String typeDossier;
private String idModele;
private List<ProjetDatasetUI> projetDatasets;
protected ProjetUI(Parcel in) {
id = in.readString();
idParent = in.readString();
nom = in.readString();
description = in.readString();
colonnes = in.createTypedArrayList(ProjetColonneUI.CREATOR);
vues = in.createTypedArrayList(VueUI.CREATOR);
archive = in.readInt() == 1;
version = in.readString();
commentaire = in.readString();
published = in.readInt() == 1;
droitAcces = in.createTypedArrayList(DroitAccesUI.CREATOR);
idDossier = in.readString();
typeDossier = in.readString();
idModele = in.readString();
projetDatasets = in.createTypedArrayList(ProjetDatasetUI.CREATOR);
}
public static final Creator<ProjetUI> CREATOR = new Creator<ProjetUI>() {
#Override
public ProjetUI createFromParcel(Parcel in) {
return new ProjetUI(in);
}
#Override
public ProjetUI[] newArray(int size) {
return new ProjetUI[size];
}
};
#Override
public int describeContents() {
return 0;
}
#Override
public void writeToParcel(Parcel parcel, int flags) {
parcel.writeString(getId());
parcel.writeString(getIdParent());
parcel.writeString(getNom());
parcel.writeString(getDescription());
parcel.writeTypedList(getColonnes());
parcel.writeTypedList(getVues());
parcel.writeInt(isArchive() ? 1 : 0);
parcel.writeString(getVersion());
parcel.writeString(getCommentaire());
parcel.writeInt(isPublished() ? 1 : 0);
parcel.writeTypedList(getDroitAcces());
parcel.writeString(getIdDossier());
parcel.writeString(getTypeDossier());
parcel.writeString(getIdModele());
parcel.writeTypedList(getProjetDatasets());
}
}
EDIT
This is the complete stacktrace
The other classes implement parcelable just like ProjeUI class.
Here is an example of another class that has an enum type and an example of an enum that implements parcelable
public class VueRelationUI implements Parcelable {
private String id;
private String idVue;
private String idRelation;
private RelationType typeRelation;
protected VueRelationUI(Parcel in) {
id = in.readString();
idVue = in.readString();
idRelation = in.readString();
typeRelation = in.readParcelable(RelationType.class.getClassLoader());
}
public static final Creator<VueRelationUI> CREATOR = new Creator<VueRelationUI>() {
#Override
public VueRelationUI createFromParcel(Parcel in) {
return new VueRelationUI(in);
}
#Override
public VueRelationUI[] newArray(int size) {
return new VueRelationUI[size];
}
};
#Override
public int describeContents() {
return 0;
}
#Override
public void writeToParcel(Parcel parcel, int flags) {
parcel.writeString(getId());
parcel.writeString(getIdVue());
parcel.writeString(getIdRelation());
parcel.writeParcelable(getTypeRelation(), flags);
}
}
ENUM
public enum RelationType implements Parcelable {
INNER,
OUTER;
#Override
public void writeToParcel(Parcel parcel, int flags) {
parcel.writeInt(ordinal());
}
#Override
public int describeContents() {
return 0;
}
public static final Creator<RelationType> CREATOR = new Creator<RelationType>() {
#Override
public RelationType createFromParcel(Parcel parcel) {
return RelationType.values()[parcel.readInt()];
}
#Override
public RelationType[] newArray(int size) {
return new RelationType[size];
}
};
}
Any help would be much appreciated
The problem happens because of the internal implementation of Android's Parcel class. When you start the new activity, all of the intent extras are parceled and then unparceled. When this happens, the Android framework allocates a new Parcelable[], and not a new ProjetUI[]. So you get a ClassCastException when you try to cast it.
Probably the best solution would be to change your code to use ArrayList<ProjetUI> instead of ProjetUI[]. Then you can use Intent.putParcelableArrayListExtra() and getParcelableArrayListExtra() without any problems.
If you can't do that for some reason, then you will have to manually cast the array one element at a time:
Parcelable[] parcelables = getIntent().getParcelableArrayExtra(ARG_PROJECTS);
ProjetUI[] mProjects = new ProjetUI[parcelables.length];
for (int i = 0; i < parcelables.length; ++i) {
mProjects[i] = (ProjetUI) parcelables[i];
}
I have a Parcelable object that has a list of Parcelable objects. I am trying to read that list back after it has been passed from one Activity to the next, but only the first element is "un-bundled"
public class MyBundle implements Parcelable {
private List<Data> dataList;
public static final Parcelable.Creator<MyBundle> CREATOR = new Parcelable.Creator<MyBundle>() {
public MyBundle createFromParcel(Parcel in) {
return new MyBundle(in);
}
public MyBundle[] newArray(int size) {
return new MyBundle[size];
}
};
public MyBundle() {
}
public MyBundle(Parcel in) {
//dataList = new ArrayList<>();
//in.readTypedList(dataList, Data.CREATOR);
dataList = in.createTypedArrayList(Data.CREATOR);
//BOTH have the same result
}
#Override
public int describeContents() {
return 0;
}
#Override
public void writeToParcel(Parcel dest, int flags) {
if (dataList != null && dataList.size() > 0) {
dest.writeTypedList(dataList);
}
}
}
The data object:
/*BaseObject has the following properties:
UUID uuid;
long databaseId;
createdDate;
modifiedDate;
*/
public class Data extends BaseObject implements Parcelable {
private String name;
private String serial;
private String location;
public Data() {}
private Data(Parcel in) {
String uuidString = in.readString();
if (uuidString == null) return; //this is null!
uuid = UUID.fromString(idString);
databaseId = in.readLong();
createdDate = new Date(in.readLong());
modifiedDate = new Date(in.readLong());
location = in.readString();
name = in.readString();
serial = in.readString();
}
#Override
public int describeContents() {
return 0;
}
#Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(uuid.toString());
dest.writeLong(databaseId);
dest.writeLong(createdDate.getTime());
dest.writeLong(modifiedDate.getTime());
dest.writeString(name);
dest.writeString(serial);
}
public static final Parcelable.Creator<Data> CREATOR
= new Parcelable.Creator<Data>() {
public Data createFromParcel(Parcel in) {
return new Data(in);
}
public Data[] newArray(int size) {
return new Data[size];
}
};
}
What I have tried:
Debugging - I can see the first element is read fine but the rest are return null, and they do have values when they are being written
"Android, How to use readTypedList method correctly in a Parcelable class?"
"how to properly implement Parcelable with an ArrayList?"
So this is the answer: My Data parcelable misses the location element when it creates the parcel. This obviously results in some kind of offset error when READING occurs. So the coded solution is as follows:
#Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(uuid.toString());
dest.writeLong(databaseId);
dest.writeLong(createdDate.getTime());
dest.writeLong(modifiedDate.getTime());
dest.writeString(location); /*HERE!*/
dest.writeString(name);
dest.writeString(serial);
}
I hope this helps someone else.
I am using Parcelable to communicate between fragments. Everything is working good, but sometimes when Android kills app process to free up the memory and user returns to app, the Parcelable gets huge amount of items from arrays like couple millions items instead of 2-3 and of course it throws OOM.
I suppose something's wrong with CREATOR, but I can't handle it.
The code for parcelable is composed from super class and child class and my interested item class:
base class:
public abstract class BaseRs implements Parcelable {
private String token;
private String msgAlert;
private String simpleMsg;
private AdsObj adsObj;
private Map<String, String> mapSettings;
//getters & setters
public BaseRs() {
}
protected BaseRs(Parcel in) {
token = in.readString();
msgAlert = in.readString();
simpleMsg = in.readString();
adsObj = in.readParcelable(AdsObj.class.getClassLoader());
mapSettings = MapParcelable.readParcelable(in);
}
#Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(token);
dest.writeString(msgAlert);
dest.writeString(simpleMsg);
dest.writeParcelable(adsObj, flags);
MapParcelable.writeToParcel(dest, mapSettings);
}
}
child class
public class GetSalesItemsRs extends BaseRs {
private SaleCoinItem[] coinPacksArray;
//getters+ setters
protected GetSalesItemsRs(Parcel in) {
super(in);
coinPacksArray = in.createTypedArray(SaleCoinItem.CREATOR);
}
#Override
public void writeToParcel(Parcel dest, int flags) {
super.writeToParcel(dest, flags);
dest.writeTypedArray(coinPacksArray, flags);
}
#Override
public int describeContents() {
return 0;
}
public static final Creator<GetSalesItemsRs> CREATOR = new Creator<GetSalesItemsRs>() {
#Override
public GetSalesItemsRs createFromParcel(Parcel in) {
return new GetSalesItemsRs(in);
}
#Override
public GetSalesItemsRs[] newArray(int size) {
return new GetSalesItemsRs[size];
}
};
}
and my interested object
public class SaleCoinItem implements Parcelable {
private int amount;
private double price;
private int sortOrder;
//getters & setters
protected SaleCoinItem(Parcel in) {
amount = in.readInt();
price = in.readDouble();
sortOrder = in.readInt();
}
#Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(amount);
dest.writeDouble(price);
dest.writeInt(sortOrder);
}
#Override
public int describeContents() {
return 0;
}
public static final Creator<SaleCoinItem> CREATOR = new Creator<SaleCoinItem>() {
#Override
public SaleCoinItem createFromParcel(Parcel in) {
return new SaleCoinItem(in);
}
#Override
public SaleCoinItem[] newArray(int size) {
return new SaleCoinItem[size];
}
};
}
The functiona to parcel Map - maybe here is the error ;)
public class MapParcelable {
public static void writeToParcel(Parcel out, Map<String, String> map) {
if (map != null) {
out.writeInt(map.size());
for (Map.Entry<String, String> entry : map.entrySet()) {
out.writeString(entry.getKey());
out.writeString(entry.getValue());
}
}
}
public static Map<String, String> readParcelable(Parcel parcel) {
Map<String, String> map = new HashMap<String, String>();
int size = parcel.readInt();
for (int i = 0; i < size; i++) {
String key = parcel.readString();
String value = parcel.readString();
map.put(key, value);
}
return map;
}
}
I get parcelable obj inside the onCreateView method :
GetSalesItemsRs mGetSalesItemsRs = getArguments().getParcelable(KEY_PARCEL);
I think the problem is that if the Map is null you're not writing the size field to the Parcel, but when you read it you always read the size field, and it might end up reading some random value. So or you change the code to
if (map != null) {
out.writeInt(map.size());
for (Map.Entry<String, String> entry : map.entrySet()) {
out.writeString(entry.getKey());
out.writeString(entry.getValue());
}
}else{
out.writeInt(0);
}
and you will never get a null value when recreating the class, or you create another boolean field "hasMap" and always write that one
I have written following code in my Activity Class,
ArrayList<TXData> data;
data= Map.getDataList();
Bundle bundle = new Bundle();
bundle.putParcelableArrayList("data",data);
Fragment fragment = new MapsFragment();
fragment.setArguments(bundle);
In MapsFragment, I have written following code to get data on onActivityCreated() Method.
ArrayList<TXData> dataList;
dataList=getArguments().getParcelableArrayList("data");
It returns following exception on Fragment class.
Caused by: java.lang.NullPointerException: Attempt to invoke virtual
method java.util.ArrayList
android.os.Bundle.getParcelableArrayList(java.lang.String) on a null
object reference
I added debug points, to find out the cause of problem, It successfully adds the data to Bundle from Activity, but its not getting any data in Fragment.
Kindly guide me what I am doing wrong here.
EDIT
Parceable Class
public class TXData implements Serializable, Parcelable{
public String id;
public String title;
public String pId = "";
public float heading;
public float pitch;
public int totalCount;
int mData;
public TXSData(){
}
protected TXData(Parcel in) {
id = in.readString();
title = in.readString();
pId = in.readString();
heading = in.readFloat();
pitch = in.readFloat();
totalCount = in.readInt();
mData = in.readInt();
minPrice = in.readInt();
maxPrice = in.readInt();
isInFilter = in.readByte() != 0;
isEnabled = in.readByte() != 0;
totalCount = in.readInt();
}
public static final Creator<TXData> CREATOR = new Creator<TXData>() {
#Override
public TXData createFromParcel(Parcel in) {
return new TXData(in);
}
#Override
public TXData[] newArray(int size) {
return new TXData[size];
}
};
#Override
public int describeContents() {
return 0;
}
#Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(mData);
}
The easy solution would be to drop the Parcelable and stay with Serializable.
public class TXData implements Serializable{
public String id;
public String title;
public String pId = "";
public float heading;
public float pitch;
public int totalCount;
int mData;
}
and use putExtra/getSerializableExtera instead of getParcelableArrayList/setParcelableArrayList
For big amounts of data, it is recommended to use Parcelable which is built for speed, see this post or similar...
Replace below class.
public class TXData implements Parcelable {
public String id;
public String title;
public String pId = "";
public float heading;
public float pitch;
public int totalCount;
int mData;
public TXSData(){
}
protected TXData(Parcel in) {
id = in.readString();
title = in.readString();
pId = in.readString();
heading = in.readFloat();
pitch = in.readFloat();
totalCount = in.readInt();
mData = in.readInt();
}
#Override
public int describeContents() {
return 0;
}
#Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(id);
dest.writeString(title);
dest.writeString(pId);
dest.writeFloat(heading);
dest.writeFloat(pitch);
dest.writeInt(totalCount);
dest.writeInt(mData);
}
#SuppressWarnings("unused")
public static final Parcelable.Creator<TXData> CREATOR = new Parcelable.Creator<TXData>() {
#Override
public TXData createFromParcel(Parcel in) {
return new TXData(in);
}
#Override
public TXData[] newArray(int size) {
return new TXData[size];
}
};
}
Can you try that.
ArrayList<TXData> dataList = new ArrayList<>();
dataList.addAll(getArguments().getParcelableArrayList("data"));
Your writeToParcel method is incomplete.
Your writeToParcel should be writing all the properties in the same order as it is in the parameterized contructor: protected TXSectionData(Parcel in)
Check here: tutorial
One very important thing to pay close attention to is the order that you write and read your values to and from the Parcel. They need to match up in both cases. The mechanism that Android uses to read the Parcel is blind and completely trusts you to get the order correct, or else you will run into run-time crashes.
Try getting the Bundle object (getArguments()) in onCreate() or onCreateView()
of the fragment and not onActivityCreated().
Refer to the example from Fragment documentation.
After consuming a json with Retrofit and using unsynchronous callback I can't pass An ArrayList from The MainActivity to a fragment .
Code from MainActivity:
lFragmentManager = getFragmentManager();
lFragment = lFragmentManager.findFragmentById(R.id.frame_container);
lFragment = new Fragment_Categories();
Bundle bundle = new Bundle();
bundle.putParcelableArrayList("list_categories", Categories_.getCategories());
//for(int a = 0 ; a < Categories_.getCategories().size(); a++)
// Log.d("billy",Categories_.getCategories().get(a).getTitle());
lFragment.setArguments(bundle);
lFragmentManager.beginTransaction().replace(R.id.frame_container ,lFragment ).commit();
Note that the Log inside comments does print the context of the list, so I take the ArrayList in the fragment with this code inside onCreateView:
if(savedInstanceState != null)
categories = savedInstanceState.getParcelableArrayList("list_categories");
/*
* initialize the Recycler view
*/
mRecycler = (RecyclerView)view.findViewById(R.id.categories_list);
mAdapter = new AdapterCategories(categories,getActivity());
mRecycler.setAdapter(mAdapter);
Here is my Class Category :
public class Categories implements Parcelable{
private ArrayList<NavDrawerItem> categories;
#Override
public int describeContents() {
return 0;
}
#Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeTypedList(categories);
}
public static final Parcelable.Creator<Categories> CREATOR = new Parcelable.Creator<Categories>() {
public Categories createFromParcel(Parcel in) {
return new Categories(in);
}
public Categories[] newArray(int size){
return new Categories[size];
}
};
private Categories(Parcel in){
categories = in.createTypedArrayList(NavDrawerItem.CREATOR);
}
public ArrayList<NavDrawerItem> getCategories() {
return categories;
}
}
And here is the Class NavDrawerItem:
public class NavDrawerItem implements Parcelable {
private String title;
private String description;
private String image;
private String post_count;
private String id;
private String slug;
private int parent;
#Override
public int describeContents() {
return 0;
}
#Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(title);
dest.writeString(description);
dest.writeString(image);
dest.writeString(post_count);
dest.writeString(id);
dest.writeString(slug);
dest.writeInt(parent);
}
public static final Parcelable.Creator<NavDrawerItem> CREATOR = new Parcelable.Creator<NavDrawerItem>() {
public NavDrawerItem createFromParcel(Parcel in) {
return new NavDrawerItem(in);
}
public NavDrawerItem[] newArray(int size){
return new NavDrawerItem[size];
}
};
private NavDrawerItem(Parcel in){
title = in.readString();
description = in.readString();
image = in.readString();
post_count = in.readString();
id = in.readString();
slug = in.readString();
parent = in.readInt();
}
The problem is that inside fragment (when trying to pass the list to the Adapter of the RecyclerView) or inside the adapter of the RecyclerView I take a null exception :
java.lang.NullPointerException: Attempt to invoke virtual method 'java.util.ArrayList android.os.Bundle.getParcelableArrayList(java.lang.String)' on a null object reference
Thank you for the help!!
You wouldn't use the savedInstanceState to get your arguments, because they weren't passed as part of a saveInstanceState() operation. You want to use getArguments() instead. So try this...
categories = getArguments().getParcelableArrayList("list_categories");