On every launch my app loads fresh data from parse.com and pins all objects to use app without network requests like this^
public void pinKids() {
ParseQuery<ParseObject> query = ParseQuery.getQuery(ParseConstants.CLASS_KIDS);
query.whereEqualTo(ParseConstants.KEY_PARENT, currentUser);
query.findInBackground(new FindCallback<ParseObject>() {
public void done(final List<ParseObject> kidsList, ParseException e) {
if (e == null) {
ParseObject.unpinAllInBackground(ParseConstants.LABEL_KIDS, new DeleteCallback() {
#Override
public void done(ParseException e) {
ParseObject.pinAllInBackground(ParseConstants.LABEL_KIDS, kidsList, new SaveCallback() {
#Override
public void done(ParseException e) {
Log.d("Hello", "pinKids");
}
});
}
});
}
}
});
}
One of the columns in my class is an array of strings (it stores history messages). I found that my code doesn't refresh this array if that array was updated from the other device (the same user did something on the other device). The rest of the data (other columns) refresh without problems. But the array of strings stays as it was downloaded at first launch.
In parse.com dashboard I see that array is updated. But code doesn't download this array while all the other columns are downloaded correctly.
Let me say that when I update this array on device-1 the new data is stored on device-1 and on "parse.com" too. But it doesn't update on device-2.
Please help!
OK! I've solved it.
This problem is a bug of parse.com.
I've created an issue on parse's github.
So I found that my problem is a part of parse's bug.
So the answer is: replace all calls to getJSONArray(key) with getList(key). You might also need revert()/revert(key)
Related
I'm using Parse as my backend and I'm trying to "like" a post that another user posted on the app. I'm querying to get the post, then incrementing the number of likes by 1, then adding the current user's object ID to an array that holds all the ID's of users which liked the post.
carLikeQuery.getInBackground(carItem.getObjectId(), new GetCallback<ParseObject>() {
#Override
public void done(ParseObject object, ParseException e) {
object.increment("likes");
object.addUnique("usersWhoLike", ParseUser.getCurrentUser().getObjectId());
object.saveInBackground(new SaveCallback() {
#Override
public void done(ParseException e) {
if(e==null) {
Log.d("SAVE", "Like saved :)");
} else {
Log.e("SAVE", "Not saved :( :" + e.getLocalizedMessage());
}
}
});
}
});
The error I'm getting:
E/SAVE: Not saved :( :java.lang.IllegalArgumentException: Cannot save a ParseUser that is not authenticated.
I saw the source code for the ParseUser from somewhere:
void validateSave() {
if (getObjectId() == null) {
throw new IllegalArgumentException("Cannot save a ParseUser until it has been signed up. Call signUp first.");
}
if (!isAuthenticated() && isDirty()) {
throw new IllegalArgumentException("Cannot save a ParseUser that is not authenticated.");
}
}
Doing the same kind of checking in my code reveals that the the currentUser is AUTHENTICATED and NOT DIRTY.
What could the issue be? To be honest, I want to say that it was working just fine before today, but obviously I was changing something and made a mistake down the line and I can't find it! Any help would be greatly appreciated.
I resolved the issue by creating an entirely new Parse application with the same data structure/layout. It just plain worked without any code changes.
You can follow the issue on GitHub here
I am using the Android SDK of parse.com and have arrived at a peculiar problem.
From a fragment's onCreate:
Fetch an object from the server.
Pin it.
Fetch an object from the local datastore.
Here is a snippet of the code from the onCreate:
ParseObject myChatGroup = ParseObject.createWithoutData("ChatGroup", "mOJGWRiLPC");
myChatGroup.fetchInBackground(new GetCallback<ParseObject>() {
#Override
public void done(ParseObject chatGroup1, ParseException e) {
if (e == null) {
l.d("Fetched chat group: " + chatGroup1 + " from server");
chatGroup1.pinInBackground(new SaveCallback() {
#Override
public void done(ParseException e) {
if (e == null) {
l.d("Successfully pinned chat group");
ParseQuery<ParseObject> chatGroupParseQuery = new ParseQuery<>("ChatGroup");
chatGroupParseQuery.fromLocalDatastore()
.findInBackground(new FindCallback<ParseObject>() {
#Override
public void done(List<ParseObject> list, ParseException e) {
if (e == null) {
l.d("Found " + list.size() + " chat groups from local datastore");
} else {
e.printStackTrace();
}
}
});
} else {
e.printStackTrace();
}
}
});
} else {
e.printStackTrace();
}
}
});
Here is the log:
Fetched chat group: ChatGroup { objectId: mOJGWRiLPC, name: Admin } from server
Successfully pinned chat group
Found 0 chat groups from local datastore
But this doesn't make any sense! I just pinned an object so how can there be 0 objects in the local datastore. The code is so simple. What am I doing wrong? Could it be a bug with the SDK?
Any insight will be much appreciated I have been trying to find the issue for days now.
The Parse setup looks fine to me:
ParseObject.registerSubclass(ChatGroup.class);
Parse.enableLocalDatastore(this);
Parse.initialize(this, AppProps.properties.appId,
AppProps.properties.clientKey);
ParseUser.enableRevocableSessionInBackground();
Parse.setLogLevel(Parse.LOG_LEVEL_DEBUG);
Note:
It works fine when trying the same logic with the ParseUser object. And even other classes of mine like my Message which leads me to suspect that something is wrong with my ChatGroup class.
I have added two fields to my ChatGroup class on the parse.com data browser: name(String) and messages(Relation) with read and wrote access to a role called Admin.
When browsing to the actual parse db (using root access) I find that the database table (ParseObjects) does contain the row that I pinned. But somehow parse shows 0 results on querying it!
Parse doesn't seem to support ACLs on local datastore. So the easiest way to get around this issue is to do ignoreACLs() on the query -- assuming you do have roles/acls setup correctly on the server side, so whatever you have pinned locally should be OK permission-wise already.
I found the issue. It seems to be more like a bug with the Android SDK of parse. I narrowed it down to an issue with retrieving pinned objects with special ACLs using the Android SDK.
Initially, the object had the ACL such that only the role 'Admin' (that I created) can read and write. The funny thing is that the user with which I was testing the pinning and querying was an 'Admin'! But, when I changed the ACL such that public can read but only Admin can write, the problem was resolved!
My theory was that querying the local datastore behaves like a public query, no matter what role the user that is making the query has! But if any other theory explaining this would be appreciated. Luckily, in my case I need public read access, but this bug can be a huge downfall for some who need to have read access restricting ACLs! Hopefully parse.com will fix this issue soon.
This is quite a big issue! I am surprised that I couldn't find a question regarding this issue.
Yeah so basically you have to set ACL for current user and pin it before you pin the chat group.
ParseACL acl = new ParseACL();
acl.setReadAccess(ParseUser.getCurrentUser(), true);
ParseUser.getCurrentUser().setACL(acl);
ParseUser.getCurrentUser().pinInBackground();
I have an app which works with users, and offers the possibility to follow certain users, at the current user choice.
Unfortunately, I don't know how to modify data of a user which is not the current user. I have not seen anything like that stated in the Parse.com docs(or i missed).
I have a column in my database in "Users" class called "usersFollowed" and when the current user clicks "Follow" i want to add the current user to the "usersFollowed" list, but adding them like you will see in the following code does not work.
Follow user code:
dialog = ProgressDialog.show(context, "",
"Following...", true);
viewHolder.userFollow.setSelected(true);
viewHolder.userFollow.setText("FOLLOWING");
ParseQuery<ParseUser> userListQuery = ParseUser.getQuery();
userListQuery.whereEqualTo("screenName", parseUserList.get(position).get("screenName").toString());
userListQuery.findInBackground(new FindCallback<ParseUser>() {
#Override
public void done(List<ParseUser> parseObjects, ParseException e) {
parseUserFollowedList = new ArrayList<>();
if (parseObjects.get(0).getList("usersFollowed") == null) {
parseUserFollowedList.add(ParseUser.getCurrentUser());
parseObjects.get(0).addAll("usersFollowed", parseUserFollowedList);
} else {
parseUserFollowedList = parseObjects.get(0).getList("usersFollowed");
parseUserFollowedList.add(ParseUser.getCurrentUser());
parseObjects.get(0).addAll("usersFollowed", parseUserFollowedList);
}
parseObjects.get(0).saveInBackground(new SaveCallback() {
#Override
public void done(ParseException e) {
dialog.dismiss();
}
});
}
});
.saveInBackground does not work because it throws exception: Cannot save info for user that is not logged in".
Can anyone help me in how to do this?
Cheers!
Users may modify only their own data. But the idea of following can be implemented such that only the currentUser's record requires write access: If Jack chooses to follow Jill, then Jack writes to his "following" relation. This works as long as we don't try to represent "followedBy" in Jill's data (which can be achieved instead with a query).
Or consider that the User table represents the private relationship between a real person and your app. It might make better sense to model the idea of a user's public face with your own custom object, and model following relationships between those. I mention this idea elsewhere here and here.
I have a registered app on Parse with a signup/login system. Now I want to create an activity which grabs all the available users in the app and to display them one-by-one as a listview. Can someone tell me how can I do that?
Once you've setup your ListView's adapter you can call this whenever you need to update it's contents:
public void updateUsers() {
ParseQuery<ParseUser> query = ParseUser.getQuery();
query.findInBackground(new FindCallback<ParseUser>() {
#Override
public void done(List<ParseUser> userObjects, ParseException error) {
if (userObjects != null) {
mUserAdapter.clear();
for (int i = 0; i < userObjects.size(); i++) {
mUserAdapter.add(userObjects.get(i));
}
}
}
});
}
That updates my adapter which is plugged into my ListView. If you have more than 100 users you want to return you'll need to up the limit as I hear that's the default (then you have to page the results, not sure how yet)
Side note: As far as I know you can't Subclass the ParseUser at the moment (you can, but you then can't use that as the object you're querying with using ParseUser.getQuery() (Which would normally be done by ParseUser.getQuery(MyCustomUserClass.class) if you were to query a customised object.
Situation
I write an Android messaging app using Parse.com as back-end
There's an activity showing list of messages sent to currently logged in user
User gets push from Parse if there is new message for him on the server
I try to accomplish this in two steps:
When this activity starts (in its onCreate method), I fetch all messages for current user from Parse back-end end store them into local datastore, if there were any messages already in the datastore, they all are removed (unpinned) and the fetched messages are pinned instead
Then I ask the local datastore for the messages and with the results I refresh the list of messages (by filling ArrayList<Message> variable and calling notifyDataSetChanged on the adapter using this variable as source of data)
Why in two steps?
I have it split into two steps, because when the activity is running and user receives from Parse back-end a push that there is/are new message(s) for him, I fetch only the new messages in similar way (but not exactly like) in step 1., and then call the exact same refresh function like in step 2. For this a broadcast receiver handling the pushes is used
Step 1.
The following code snippet shows how I fetch all messages from server in step 1 in the onCreate method of the activity
ParseQuery<Message> query = ParseQuery.getQuery(Message.class);
query.whereEqualTo("recipient", ParseUser.getCurrentUser());
query.addDescendingOrder("createdAt");
query.findInBackground(new FindCallback<Message>() {
#Override
public void done(final List<Message> messages, ParseException e) {
if (e == null && messages.size() > 0) {
ParseObject.unpinAllInBackground("messages",
new DeleteCallback() {
#Override
public void done(ParseException e) {
ParseObject.pinAllInBackground("messages",
messages, new SaveCallback() {
#Override
public void done(
ParseException e) {
for (Message message : messages) {
if (message.getStatus() == 0) {
message.setStatus(1);
message.saveEventually();
}
}
refreshList();
}
});
}
});
}
}
});
The part when status is changed from 0 to 1 is to change the message from being new to unread
For sake of completeness and to document the words written in Why in two steps?, this is how I get only the new messages in the onReceive of the broadcast receiver processing the pushes from Parse back-end
Broadcast receiver snippet
public void onReceive(Context context, Intent intent) {
ParseQuery<Message> query = ParseQuery.getQuery(Message.class);
query.whereEqualTo("recipient", ParseUser.getCurrentUser());
query.whereEqualTo("status", 0);
query.addDescendingOrder("createdAt");
query.findInBackground(new FindCallback<Message>() {
#Override
public void done(final List<Message> messages, ParseException e) {
if (e == null && messages.size() > 0) {
ParseObject.pinAllInBackground("messages", messages,
new SaveCallback() {
#Override
public void done(ParseException e) {
for (Message message : messages) {
message.setStatus(1);
message.saveEventually();
}
refreshList();
}
});
}
}
});
}
Step 2. The refreshList function
private void refreshList() {
ParseQuery<Message> query = ParseQuery.getQuery(Message.class);
query.fromPin("messages");
query.whereEqualTo("recipient", ParseUser.getCurrentUser());
query.addDescendingOrder("createdAt");
query.findInBackground(new FindCallback<Message>() {
#Override
public void done(final List<Message> messages, ParseException e) {
items.clear();
for (Message m : messages) {
items.add(m);
}
adapter.notifyDataSetChanged();
}
});
}
Other variables
ArrayList<Message> items;
MessageArrayAdapter adapter;
Where MessageArrayAdapter just extends ArrayAdapter
Why do I need to use the local datastore at all?
The activity can exist for quite long time
... and the user can receive new messages when the activity is running
I don't want to ask the server every time there is new message for all messages
I know I could ask just for the new ones and add them to the items when it is member variable of the activity where all this happening. In such case, I didn't have to use the local datastore at all
The reason for using it is that in the future I plan to move the fetch all messages from the activity completely - e.g. to start of whole application or maybe to service fetching the messages on periodic basis
Is this acceptable way of solving this problem?