Parse: How to prevent pointers from being saved recursively in Android? - android

I a parse class called Booking which has pointers to Parse classes: Ticketand _User and other irrelevant fields.
Where Ticket has pointers to Location and more irrelevant fields.
Most classes other than the Booking class has Class level Access List Restrictions, which is necessary for the security of my app.
The following is the code I use to attempt to save a booking object using the android SDK:
final Booking booking = new Booking();
// For pointer like behaviour
Ticket ticketPointer = Ticket.createWithoutData(Ticket.class, bookingData.getTicket().getObjectId());
booking.setTicket(ticketPointer);
ArrayList<Coupon> couponPointers = new ArrayList<>();
for (Coupon coupon : bookingData.getCoupons()) {
// For pointer like behaviour
couponPointers.add(Coupon.createWithoutData(Coupon.class, coupon.getObjectId()));
}
booking.setCoupons(couponPointers);
booking.setClient(ParseUser.getCurrentUser());
booking.setTicketCount(bookingData.getTicketCount());
booking.setPaymentMethod(Booking.PaymentMethod.CASH_ON_ARRIVAL);
booking.saveInBackground(new SaveCallback() {
#Override
public void done(ParseException e) {
if (asserter.assertPointerQuietly(e)) {
e.printStackTrace();
} else {
l.d("Successfully posted a booking request on server");
}
}
});
The problem is that parse saves object recursively, so even objects pointed to by the pointers are saved along with the object being saved. And since the other classes can't be accessed by regular users, exceptions are raised when attempts to save these objects pointed to by the pointers are made. So, I put the ticket and coupon objects into the booking object in the form of pointers using the createWithoutData method as marked in comments as // For pointer like behaviour as per the solution to saving pointers here.
All this works well, and I got rid of exceptions raised by accessing the Ticket and the Coupon class which were saved recursively.
However, to my surprise, the above code results in another exception caused by an attempt to access the Location class which is pointed to by the pointer of the Ticket object which is pointed to by the Booking object(2nd level pointer)!!!.
Is there any way to prevent the location object from being pointed to? An ideal solution would be to disable recursive saves in the first place, but any solution would be appreciated.
A workarounds exist, but it is ugly, and I would like to avoid it: It involves using a string representation of the object id of the object instead of the pointer.

Related

firebase persist data with circular reference

I've a simple data that i want to persist on firebase.
This data is a domain class for my relational model (i started with a relational model and now im deciding whenever or not migrate to firebase, but for awhile im working with both... or trying to)
To persist a new instance if my class on firebase i need to do:
Map<String, Object> firebase = new HashMap<String, Object>();
firebase.put("raffleDate", this.giveaway.getRaffleDate());
firebase.put("thumbnailUrl", this.giveaway.getThumbnailUrl());
firebase.put("mustFollowList", this.giveaway.getMustFollowList());
firebase.put("owner", this.giveaway.getOwner());
firebase.put("amountFriendsToIndicate", this.giveaway.getAmountFriendsToIndicate());
firebase.put("mediaId", this.giveaway.getMediaId());
((App) getApplication()).getFirebase().child("giveaways").child(this.giveaway.getMediaId()).setValue(firebase);
because besides these fields Giveaway has one last other field which has a circular reference to itself
#ToMany(referencedJoinProperty = "raffle")
#Expose(deserialize = false, serialize = false)
private List<UserOnGiveaway> attendantsTickets;
This field maps the relatioship between user and its giveaways, UserOnGiveaway class has a reference to User and Giveaway so when i try to persist i get a very long non compreensive error that I can just guess is due some stackoverflow because of the circular reference
The thing is I DONT REALLY CARE ABOUT PERSISTING THIS FIELD, in my actual "hybrid" archtecture i'm using firebase only to persist data shared among users, individual user data is being stored locally on android sqlite
So i would like to know is there any way i can annotate this field to force firebase ignore it?
or is there any parameter is can set on firebase call to do it?
PLEASE do not suggest transient as it will also affect my ORM
PLEASE2 do not suggest changes on domain since i'm giving a try to firebase i wont make any structural changes before decide for it.
thanks
You can use the #Exclude annotation on a field or getter/setter method to omit it from serialization with the Firebase SDK.

RealmResult as RealmObject field

I'm trying to figure out the best way to set up a RealmObject with a RealmResult as one of its fields.
For example, let's say I have two RealmObjects, Goal and Achievement. The Goal object contains fields that define a query of Achievement's the user wants to track (e.g. date range the achievement was created, type of achievement, etc) and has custom methods to extract statistics from those Achievements.
What is the best way for Goal to contain this RealmResult of Achievements? Here are some ways I've thought of doing this:
Have a persisted RealmList field in Goal and update it anytime a field is changed that would change the resulting query. But how would this RealmList get updated if a new Achievement gets added to the realm?
Use #Ignore annotation on a RealmResult<Achievement> field within Goal. Anywhere in Goal where mResult is used, first check if null and requery if needed. This seems like I will be doing a lot of unneccessary querying if I'm using something like a RecyclerView that refetches the object in getItem().
Have a wrapper class that contains a Goal object and the RealmResult<Achievement> as fields. Add a listener to Goal so that anytime a relevant field changes the RealmResult can be requeried.
I'm leaning towards the last one as the cleanest way to keep a valid RealmResult. Am I missing an easier way to accomplish this?
Okay so I'm trying to implement a wrapper class (which I think is similar to the DAO abstraction #EpicPandaForce was mentioning, but I'm not super familiar with that)
public class GoalWrapper {
private RealmResults<Achievements> mResults;
private Goal mGoal;
private Realm mRealm;
public GoalWrapper(Realm realm, Goal goal) {
mRealm = realm;
mGoal = goal;
// TODO: does this need to be removed somewhere? What happens when GoalWrapper gets GC'd?
goal.addChangeListener(new RealmChangeListener<RealmModel>() {
#Override
public void onChange(RealmModel element) {
// rerun the query
findResultForGoal();
}
});
findResultForGoal();
}
/**
* Run a query for the given goal and calculate the result
*/
private void findResultForGoal() {
mResults = mRealm.where(Achievement.class)
.greaterThanOrEqualTo("date", mGoal.getStartDate())
.lessThanOrEqualTo("date", mGoal.getEndDate())
.equalTo("type", mGoal.getAchievementType())
.findAll();
calculateStats();
}
private void calculateStats() {
// Get relevant stats from mResult...
}
}
I haven't tested this code yet but I plan to have a RecyclerView.Adapter with an ArrayList of GoalWrapper objects.
My one concern is that I never remove the listener on mGoal. Do I even need to remove it? What happens in the case that the ArrayList gets GC'ed? I would think that the Goal field and resulting listeners attached to it all get GC'ed as well.

Local datastore write methods do not return (e.g..: pin(), pinAll(), unpin(), unpinAll())

I'm building an android app using the Android Parse SDK, which gets all data from Parse at initialisation and stores it locally. Later, it will only update those entities (ParseObjects) which need so. I'm not getting any return from some Pin() operations, and similarly no callback when I use PinInBackground() and variants. Same happens with Unpin().
My code is something like the following. I have a list of ControlDate, a ParseObject which contains updated_at and updated_locally_at for each Parse data table. I use it to decide if I should query a given table (reducing number of queries). I iterate over this list when I perform a data update, in an IntentService, like this:
protected void onHandleIntent(Intent intent) {
for(ControlDate d : mUpdateQueue) { // mUpdateQueue is the list of ControlDate
if(d.entityNeedsUpdate()) { // returns true if updated_at > updated_locally_at
updateEntity(d);
}
}
private boolean updateEntity(ControlDate d) {
String tableName = d.getTableName();
Date lastLocalUpdate = d.getLastLocalUpdate();
ParseQuery<ParseObject> qParse = ParseQuery.getQuery(tableName);
qParse.whereGreaterThan("updated_at", lastLocalUpdate);
try {
// update the entities
List<ParseObject> entities = qParse.find();
ParseObject.pinAll(entities); // SOMETIMES GETS STUCK (no return)
// update the associated ControlDate
d.setLastLocalUpdate(new Date()); // updated_locally_at = now
d.pin(); // SOMETIMES GETS STUCK (no return)
}
catch(ParseException e) {
// error
}
}
}
Those operations SOMETIMES do not return. I'm trying to find a pattern but still no luck, apparently it started happening when I added pointer arrays to some of the entities. Thus, I think it may be due to the recursive nature of pin(). However it is strange that it sometimes also gets stuck with ParseObjects which do not reference any others - as it is the case with d.pin().
Things I've tried:
changing the for loop to a ListIterator (as I am changing the list of ControlDates, but I don't think this is necessary);
using the threaded variants (eg.: PinInBackground()) - no callback;
pinning each entity individually (in a loop, doing pin()) - a lot slower, still got stuck;
debugging - the thread just blocks here: http://i.imgur.com/oBDjpCw.png?1
I'm going crazy with this, help would be much appreciated!
PS.: I found this https://github.com/BoltsFramework/Bolts-Android/issues/48
Its an open issue on the bolts library, which is used in the Android SDK and may be causing this (maybe?). Anyway I cannot see how I could overcome my problem even though the cause for the pin() not returning could be an "unobserved exception" leading to a deadlock.

Update user fields with Parse

How can I update a user field using Parse as backend? Here is the code:
for(ParseUser user : mFriends) {
if (user.get("phoneNumber").toString().equals(result.substring(3))) {
user.increment("field");
user.saveInBackground();
}
}
The problem is that if I use the current user (the user who's using the application) instead of user, it works, but I want to update the field of another user.
You are not allowed to modify objects you do not own unless you set up public ACLs for that object.
Please see Object-Level Access Control and ParseACL for details.
In addition, you should probably look into Relational Data to better model your data. You really should not be modifying a direct field of another User object.

How to make sure saving ParseObject and ParseRelation using saveEventually()

I have started using Parse library recently for Android app. I want to store user contacts using saveEventually and then use the ParseRelation to relate the same to user. As there are multiple contacts mapped to user, I am using below code to handle my save functionality.
ParseRelation relation = ParseUser.getCurrentUser().getRelation(relationshipName);
for(int entityIndex = 0; entityIndex < entities.length;entityIndex++) {
...
entity[entityIndex].saveEventually(); relation.add(entity[entityIndex]);
...
}
ParseUser.getCurrentUser().saveEventually();
Here I am using saveEventually() for each valid entity (ParseObject) and then adding the same to relation. Later once all the objects are added to ParseRelation, at the end I am calling saveEventually() for ParseUser to store all the relationship to Parse.
Is this approach right? I am getting below exception at relation.add(entity[entityIndex]);
All objects in a relation must have object ids.
It seems this suggest some network connectivity issue and ParseRelation is not getting unique objectId for each ParseObject, but I was assuming that this saveEventuall() will handle this scenario well with ParseRelation
Kindly suggest. I am using Parse library version 1.1.11
Thanks.
Any object that's added to a ParseRelation must be saved first. The saveEventually call is non-blocking, so it's unlikely that the object will already have been saved by the time execution reaches the very next line when it's added to a ParseRelation.
Since you need to make sure the object is saved first, you should use saveInBackground instead of saveEventually. Then make sure to add the saved object to the relation from inside saveInBackground's SaveCallback. This will ensure that the object has been saved before being added to the relation.

Categories

Resources