Parse Array [ How to prevent user from voting multiple times] - android

I have an app in which you can upvote things, similar to Facebook likes or Reddit upvotes. You get the picture.
Well, I figured it would just be simple. I have an array column in Parse. For every message that gets an upvote, the user's name gets added to the array in that specific post's row. Except I need to prevent the user from voting an infinite amount of times. Here is what I tried after reading
final ParseQuery<ParseObject> query3 = ParseQuery.getQuery("Messages");
query3.whereContains("objectId", objectId);
query3.whereContains("PostVoteUsers", mCurrentUser.getUsername());
query3.findInBackground(new FindCallback<ParseObject>() {
List<String> list11 = new ArrayList<String>();
public void done(List<ParseObject> messages, ParseException e) {
if (e == null) {
ParseObject p = messages.get(0);
if(p.getList("PostVoteUsers").contains(mCurrentUser.getUsername())==false)
list11 = p.getList("ParseVoteUsers");
if(list11.contains(mCurrentUser.getUsername()))
uparrow.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
ParseQuery<ParseObject> query2 = ParseQuery.getQuery("Messages");
query2.getInBackground(objectId, new GetCallback<ParseObject>() {
public void done(ParseObject messages, ParseException e) {
if (e == null) {
int votes = messages.getInt("PostVotes") + 1;
messages.add("PostVoteUsers", mCurrentUser.getUsername());
messages.put("PostVotes", votes);
postvote.setText("" + votes);
messages.saveInBackground();
}
}
});
}
});
}}});
It doesn't work. Everything I try gets me with a nullpointer exception. I've tried a few other things too, but I figured I may be looking at the wrong questions.
In the class "Messages," I have an array column called "PostVoteUsers."
Once and for all, how can I find out if the user is in a specific row's array?
http://i.stack.imgur.com/a2AEq.jpg

Write a function that determines whether the user is in your object's array similar to this:
public boolean canBeLiked() {
String ids = getJSONArray("likedBy").toString();
if (ids.contains(ParseUser.getCurrentUser().getObjectId())) {
return true;
}
return false;
}
But change that to work for you. It works for me. Get the JSONArray of your ParseObject, make it a string, and then call contains(ParseUser.getCurrent().getObjectId()) on that string.

Related

RecyclerView element update + async network call

I have a recyclerview which works as expected. I have a button in the layout that fills the list. The button is supposed to make a async call, and on result, I change the button's look. All this happens fine.
But when I click on the button and scroll down the list fast, the async call's result updates the new view's button(the view that is in place of the old one). How do I handle this? Can I have a handle on when a particular view gets reused?
Update :
Code piece of the adapter class that does the async call and the updation of ui.
#Override
public void onBindViewHolder(CommentsViewHolder holder, int position) {
try {
Comments comment = comments.get(position);
holder.bindView(comment,position);
}
catch(Exception ex){ex.printStackTrace();}
}
#Override
public int getItemCount() {
if(comments==null)
{return 0;}
return comments.size();
//return comments.length();
}
public class CommentsViewHolder extends RecyclerView.ViewHolder {
TextView score ;
TextView commentText;
TextView commentTime;
TextView avatarId;
ImageButton minusOne;
ImageButton plusOne;
ParseObject model;
public CommentsViewHolder(View itemView) {
super(itemView);
//itemView.setBackgroundColor(Color.DKGRAY);
minusOne =(ImageButton)itemView.findViewById(R.id.decScore);
plusOne =(ImageButton)itemView.findViewById(R.id.incScore);
commentText = (TextView)itemView.findViewById(R.id.comment);
score = (TextView)itemView.findViewById(R.id.commentScore);
commentTime =(TextView)itemView.findViewById(R.id.commentTime);
avatarId = (TextView)itemView.findViewById(R.id.ivUserAvatar);
}
public void bindView(Comments comment, int position) {
commentText.setText(comment.getCommentText());
score.setText(Integer.toString(comment.getScore()));
String timeText = DateUtils.getRelativeTimeSpanString( comment.getCreatedAt().getTime(), System.currentTimeMillis(), DateUtils.SECOND_IN_MILLIS).toString();
timeText = timeText.replace("hours","hrs");
timeText = timeText.replace("seconds","secs");
timeText = timeText.replace("minutes","mins");
commentTime.setText(timeText);
int commentHandler = comment.getCommenterHandle();
String commenterNumber = "";
if(commentHandler==0)
{
commenterNumber = "OP";
}
else{
commenterNumber = "#"+commentHandler;
}
avatarId.setText( commenterNumber);
model = comment;
String choice = "none";
minusOne.setEnabled(true);
plusOne.setEnabled(true);
minusOne.setVisibility(View.VISIBLE);
plusOne.setVisibility(View.VISIBLE);
for (ParseObject choiceIter : choices) {
if ((choiceIter.getParseObject("comment").getObjectId()).equals(comment.getObjectId())) {
choice = choiceIter.getString("userChoice");
break;
}
}
Log.i("debug",comment.getCommentText()+" "+comment.getScore()+" "+choice);
switch (choice) {
case "plusOne":
Log.i("darkplus","setting darkplus");
plusOne.setImageResource(R.drawable.ic_add_circle_black_18dp);
plusOne.setOnClickListener(reversePlusOneOnClickListener);
//minusOne.setOnClickListener(minusOneOnClickListener);
minusOne.setVisibility(View.GONE);
break;
case "minusOne":
Log.i("darkminus","setting darkminus");
minusOne.setImageResource(R.drawable.ic_remove_circle_black_18dp);
minusOne.setOnClickListener(reverseMinusOneOnClickListener);
//plusOne.setOnClickListener(plusOneOnClickListener);
plusOne.setVisibility(View.GONE);
break;
case "none":
Log.i("darkregular","setting regular");
minusOne.setImageResource(R.drawable.ic_remove_black_18dp);
plusOne.setImageResource(R.drawable.ic_add_black_18dp);
plusOne.setOnClickListener(plusOneOnClickListener);
minusOne.setOnClickListener(minusOneOnClickListener);
break;
}
}
View.OnClickListener reversePlusOneOnClickListener = new View.OnClickListener() {
#Override
public void onClick(View v) {
if (!FourUtils.isConnected(v.getContext())) {
return;
}
minusOne.setEnabled(false);
plusOne.setEnabled(false);
model.increment("plusOne", -1);
model.increment("score", -1);
model.saveEventually(new SaveCallback() {
#Override
public void done(ParseException e) {
if (e == null) {
ParseQuery<ParseObject> query = ParseQuery.getQuery("CommentChoice");
query.whereEqualTo("user", ParseUser.getCurrentUser());
query.whereEqualTo("comment", model);
query.fromPin(Four.COMMENT_CHOICE);
query.getFirstInBackground(new GetCallback<ParseObject>() {
#Override
public void done(ParseObject parseObject, ParseException e) {
if (e == null) {
if (parseObject == null) {
parseObject = ParseObject.create("CommentChoice");
parseObject.put("comment", model);
parseObject.put("user", ParseUser.getCurrentUser());
}
parseObject.put("userChoice", "none");
parseObject.pinInBackground(Four.COMMENT_CHOICE, new SaveCallback() {
#Override
public void done(ParseException e) {
if (e == null) {
score.setText(Integer.toString(model.getInt("score")));
//votes.setText((model.getInt("minusOne") + model.getInt("plusOne")) + " votes");
minusOne.setVisibility(View.VISIBLE);
plusOne.setImageResource(R.drawable.ic_add_black_18dp);
plusOne.setOnClickListener(plusOneOnClickListener);
minusOne.setEnabled(true);
plusOne.setEnabled(true);
// minusOne.setOnClickListener(minusOneOnClickListener);
BusProvider.getInstance().post(new NewCommentChoicesAdded());
} else {
e.printStackTrace();
}
}
});
}
else{e.printStackTrace();}
}
});
} else {
e.printStackTrace();
Log.i("plus1 error", e.getMessage());
}
}
});
}
};
When the async code is done, you should update the data, not the views. After updating the data, tell the adapter that the data changed. The RecyclerView gets note of this and re-renders your view.
When working with recycling views (ListView or RecyclerView), you cannot know what item a view is representing. In your case, that view gets recycled before the async work is done and is assigned to a different item of your data.
So never modify the view. Always modify the data and notify the adapter. bindView should be the place where you treat these cases.
Chet Haase from Google discusses your exact issue in this DevBytes video.
In short, the framework need to be notified that one of the Views is in "transient" state. Once notified, the framework will not recycle this View until its "transient" flag cleared.
In your case, before you execute the async action, call setHasTransientState(true) on the child View that should change when the async action completes. This View will not be recycled until you explicitly call setHasTransientState(false) on it.
Offtopic:
It looks like you might be manipulating UI elements from background threads. Don't do that! If you can have a reference to Activity then use its runOnUiThread(Runnable action) API instead. If getting a reference to Activity is difficult, you can obtain UI thread's Handler and use its post(Runnable action) API.
Without code to look at, this is going to be difficult (if not impossible) for people to provide an exact answer. However, based on this description it sounds as though your async network loading (using an AsyncTask or custom Loader?) is not specifically tied to an element being tracked by your adapter. You'll need to have some way of tying the two together since the child View objects shown by the RecyclerView are re-used to be more efficient. This also means that if a View is being reused and there is an active async operation tied to it, that async operation will need to be canceled. Otherwise you'll see what you see now: the wrong child View being updated with content from an older async call.

Pinning live items (subclass ParseObject) and immediately retrieving via fromPin(pinName) - the list is empty

I have a very simple fragment that basically calls a method that tries to retrieve custom Parse objects from the internet, pin them and then reload a UI ListView with the pinned items whether or not the internet call succeeded (so there is a fallback caching mechanism in case there is no internet connection).
Here are the two key methods:
// This is called in onViewCreated() and onResume() of the fragment
private void reloadWalletsFromInternet() {
ParseQuery<Wallet> queryLiveData = ParseQuery.getQuery(Wallet.class);
queryLiveData.findInBackground(new FindCallback<Wallet>() {
#Override
public void done(final List<com.hasmobi.money.models.Wallet> list, ParseException e) {
if (list != null && list.size() > 0) {
for (final Wallet w : list) {
w.pinInBackground("wallets", new SaveCallback() {
#Override
public void done(ParseException e) {
reloadWalletsFromLocalstore();
}
});
}
} else {
Log.d(getClass().getSimpleName(), "no wallets retrieved from internet");
reloadWalletsFromLocalstore();
}
}
});
}
private void reloadWalletsFromLocalstore() {
final ParseQuery<Wallet> queryLocalData = ParseQuery.getQuery(Wallet.class);
queryLocalData.fromLocalDatastore();
queryLocalData.fromPin("wallets");
queryLocalData.findInBackground(new FindCallback<Wallet>() {
#Override
public void done(List<Wallet> list, ParseException e) {
// Here I am receiving 0 items in "list" which is wrong
// (e is "null")
}
});
}
Inside App.java (my custom Application base class) I've subclassed the Wallet class to be registered in Parse:
ParseObject.registerSubclass(Wallet.class);
Parse.enableLocalDatastore(this);
Parse.initialize(this, "my keys", "my keys");
I've put a couple of strategic Log.d() lines on a few places and the code successfully runs through both methods and the for() loop inside reloadWalletsFromInternet() successfully runs and appears to pin each of the received Wallet objects in the pin group "wallets". However, the subsequent query for the pins in that group, made by reloadWalletsFromLocalstore() don't seem to be able to retrieve those Wallet objects.
Below code will do the job.
get rid of queryLocalData.fromPin("wallets");
private void reloadWalletsFromLocalstore() {
final ParseQuery<Wallet> queryLocalData = ParseQuery.getQuery(Wallet.class);
queryLocalData.fromLocalDatastore();
queryLocalData.findInBackground(new FindCallback<Wallet>() {
#Override
public void done(List<Wallet> list, ParseException e) {
// Here I am receiving 0 items in "list" which is wrong
// (e is "null")
}
});
}

Using popBackStack() in Android does not update android-listview with Firebase data

At the beginning of the chat app user see a list off groups (listview group) available and the user have the possibility to create a new group or click on some off the available groups and then start to write messages (listview messages). The functions CreateNewMessage and CreateNewGroup pushes information to firebase correctly
Above scenarios works finne problems arise when user navigates backwards (popBackStack()) from listview with messages to GroupFragment, here should user be presented a list off available groups but the listview is empty. The ReadGroupData() function is not reading the already created groups from firebase and inserts them in the group listview. How to make this happen?
GroupFragment:
public void ReadGroupData() {
Firebase firebaserootRef = new Firebase("https://000.firebaseio.com");
firebaserootRef.addChildEventListener(new ChildEventListener() {
#Override
public void onChildAdded(DataSnapshot snapshot, String s) {
if (snapshot.getValue() != null) {
Group newGroup = new Group((String)snapshot.child("name").getValue(),
(String) snapshot.child("id").getValue());
if(!groupKeyValues.contains(newGroup.GetId())) {
groupKeyValues.add(newGroup.GetId());
AddToLstViewGroup(newGroup);
System.out.println("Read group data from firebase and
inserted in listView");
}
}
}
});
}
public void AddToLstViewGroup(Group newGroup) {
groupNameList.add(newGroup);
if(groupAdapter == null) {
groupAdapter = new GroupAdapter(getActivity(), groupNameList);
}
if (lstViewGroup == null) {
lstViewGroup = (ListView) getView().
findViewById(R.id.listView_group);
}
lstViewGroup.setOnItemClickListener(onItemClickListener);
lstViewGroup.setOnItemLongClickListener(onItemLongClickListener);
groupAdapter.notifyDataSetChanged();
lstViewGroup.setAdapter(groupAdapter);
}
ChatFragment:
public void ReadChatMessages(Firebase firebaseRootRef) {
firebaseRootRef.addChildEventListener(new ChildEventListener() {
#Override
public void onChildAdded(DataSnapshot snapshot, String s) {
if (snapshot.child(GetGroupId()).child("messages").
getChildren() != null) {
for (DataSnapshot c :
snapshot.child(GetGroupId()).child("messages").getChildren()) {
String key = c.getKey();
Message newMessage = new Message();
newMessage.SetFrom((String) c.child("from").getValue());
newMessage.SetMsg((String)
c.child("message").getValue());
newMessage.SetTime((String) c.child("time").getValue());
newMessage.SetId((String) c.child("id").getValue());
if ((!msgKeyValues.contains(key)) ||
newMessage.GetFrom() != "") {
msgKeyValues.add(key);
AddToLstViewChat(newMessage);
//Automatic scrolls to last line in listView.
lstViewChat.setSelection(chatAdapter.getCount() -1);
}
}
}
}
public void AddToLstViewChat(Message newMessage) {
chatMsgList.add(newMessage);
if (chatAdapter == null) {
chatAdapter = new ChatAdapter(getActivity(), chatMsgList);
}
if(IsMsgFromMe(newMessage)) {
lstViewChat = (ListView)
getView().findViewById(R.id.listView_chat_message_me);
} else {
lstViewChat =
(ListView)getView().findViewById(R.id.listView_chat_message_others);
}
chatAdapter.notifyDataSetChanged();
lstViewChat.setAdapter(chatAdapter);
}
ChatActivity:
#Override
public void onBackPressed() {
if (getFragmentManager().getBackStackEntryCount() > 0) {
getFragmentManager().popBackStack();
} else {
finish();
}
}
For all the code click on the link: "http://pastebin.com/97nR68Rm"
SOLUTION!
Kato thank you for you patience and help. I have now found a solution for the problem. I'm calling ReadGroupData() and ReadChatMessages() at the end (before return) in my onCreateView methods. As Kato pointed out onCreate() is not getting called on popBackStack()
In my AddToLStViewGroup the if statement for lstViewGroup is deleted so now it always sets the listView otherwise it will throw an exception for not finding the correct view, To clarifying:
Deleted this line:
if (lstViewGroup == null) {
lstViewGroup = (ListView)getView().findViewById(R.id.listView_group);
}
And replaced with:
ListView lstViewGroup=(ListView)getView().findViewById(R.id.listView_group);
Kato thank you for you patience and help. I have now found a solution for the problem. I'm calling ReadGroupData() and ReadChatMessages() at the end (before return) in my onCreateView methods. As Kato pointed out onCreate() is not getting called on popBackStack()
In my AddToLStViewGroup the if statement for listViewGroup is deleted so now it always sets the listView otherwise it will throw an exception for not finding the correct view.
To clarify:
I deleted this line:
if (lstViewGroup == null) {
lstViewGroup = (ListView)getView().findViewById(R.id.listView_group);
}
And replaced it with:
ListView lstViewGroup =(ListView)getView().findViewById(R.id.listView_group);
(The original asker posted the answer as part of the question. I'm copying it here as a matter of housekeeping.)

Parse findInBackground does not work

The find on the db does not complete, so the FindCallback.done() method is never called, no errors, nothing, just is not called. this is the code
ParseQuery<ParseObject> pq = ParseQuery.getQuery("Category").setLimit(1);
pq.getFirstInBackground(new GetCallback<ParseObject>() {
#Override
public void done(ParseObject object, ParseException e) {
throw new RuntimeException();
}
});
I can see the prints before and after this portion of code, but the exception is never thrown. Please, I do really need help.
Edit: I use the parse-login before this point and it works correctly, even the saveInBackground() works correctly, the only problems I find are about queries, (find, getfirst ecc).
Resolved, I found out that the background features offered by Parse do not work if in your activity there are fragments, so you have to implement them yourself. The only strange fact is that in the beginning some function (such saveInBackground as I mentioned above) worked.
Check the ACL column in the parse object row, maybe you don't have permission to read.Double click on the ACL column and check the "public Read".
Thy this:
ParseQuery<ParseObject> pq = ParseQuery.getQuery("Category").setLimit(1);
pq.findInBackground(new FindCallback< ParseObject >() {
#Override
public void done(List< ParseObject > parseObjects, ParseException e) {
// Success
} else {
e.printStackTrace();
}
The pq.getFirstInBackground(new GetCallback<ParseObject>() is meant to return only one object, so you need to help it zero in on that object, for example a particular name or item.
Try adding pq.whereEqualTo("item", biscuit);.
If you want to return a list of objects you need to use a different callback.
End product should look like this.
ParseQuery<ParseObject> pq = ParseQuery.getQuery("Category").setLimit(1);
pq.whereEqualTo("item_key_here", "item_value_here");
pq.getFirstInBackground(new GetCallback<ParseObject>() {
#Override
public void done(ParseObject object, ParseException e) {
if(object != null){
//do stuff here
}
});

clearCachedResult() not working as expected

I have this query located in my ParseQueryBuilder object:
public ParseQuery<Event> eventsTypes() {
ParseQuery<Event> query = Event.getQuery();
query.setCachePolicy(ParseQuery.CachePolicy.CACHE_ELSE_NETWORK);
query.setMaxCacheAge(TimeUnit.DAYS.toMillis(1));
query.whereEqualTo(Event.owner, parse.getParseUser());
query.orderByDescending(Event.timesUsed);
return query;
}
I use it to populate a ParseQueryAdapter
and at some point I would like to add an Event and immediately show it:
#OnClick(R.id.add)
public void add(Button button) {
final Event new_type = new Event();
new_type.setOwner(parse.getParseUser());
new_type.setName("atest");
new_type.saveEventually(new SaveCallback() {
#Override
public void done(ParseException e) {
if (e == null) {
// on successfull save, clear cache
parseQueryBuilder.eventsTypes().clearCachedResult();
// and show newly added object
mAdapter.loadObjects();
Toast.makeText(getActivity(), new_type.getName(), Toast.LENGTH_SHORT).show();
}
}
});
}
I expected clearing the cache would result in a new network query, revealing the newly added item but no matter what I try, it seems it will only show the initially cached result.
Even if I try to restart my app, it shows the result from the first cache.

Categories

Resources