Android Firestore set limit to 1 vote per user - java

I'm creating my first app from scratch which is a forum app on android and I've gotten to the upvoting/downvoting part of it.
I have it set up to where my users can upvote downvote (similar to StackOverflow) however I don't know how to limit it to one vote per user.
I'm using Firebase-Firestore as a DB.
I have the upvote and downvote toggle buttons set to update the score in firestore on a button click listener in with their respective selector item.xml for each.
Could anyone shed any light on an approach to limiting a user to one vote per answer per user similar to stack overflow?
Any and all help is appreciated.
edit
Current Code before trying provided answers.
AnswerAdapter
public class AnswerAdapter extends FirestoreRecyclerAdapter<Answer, AnswerAdapter.AnswerHolder> {
private QuestionAdapter.OnItemClickListener listener;
private Context mContext;
private static final String TAG = "AnswerAdapter";
/**
* Create a new RecyclerView adapter that listens to a Firestore Query. See {#link
* FirestoreRecyclerOptions} for configuration options.
*
* #param options
*/
public AnswerAdapter(#NonNull FirestoreRecyclerOptions<Answer> options) {
super(options);
}
#NonNull
#Override
public AnswerHolder onCreateViewHolder(#NonNull ViewGroup viewGroup, int i) {
View v = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.answer_item, viewGroup, false);
return new AnswerHolder(v);
}
#Override
protected void onBindViewHolder(#NonNull final AnswerAdapter.AnswerHolder answerHolder, final int position, #NonNull final Answer model) {
answerHolder.answerItemTextView.setText(model.getAnswer());
answerHolder.answerAuthorTextView.setText(model.getAuthor());
answerHolder.answerTimeStampTextView.setText(model.getTimestamp());
answerHolder.answerScoreTextView.setText(getSnapshots().getSnapshot(position).get("answerScore").toString());
answerHolder.mAnswerUpVoteButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
// TODO: 2019-07-26 check order of operations. seems to go through upvote and then also through downvote unchecking state
if (answerHolder.mAnswerUpVoteButton.isChecked()){
answerUpVote(answerHolder, position, model);
} else {
// answerDownVote(answerHolder, position, model);
}
}
});
public class AnswerHolder extends RecyclerView.ViewHolder{
TextView answerItemTextView;
TextView answerScoreTextView;
ToggleButton answerCheckMark;
TextView answerAuthorTextView;
TextView answerTimeStampTextView;
ToggleButton mAnswerUpVoteButton;
ToggleButton mAnswerDownVoteButton;
public AnswerHolder(#NonNull final View itemView) {
super(itemView);
answerItemTextView = itemView.findViewById(R.id.answerItemTextViewId);
answerScoreTextView = itemView.findViewById(R.id.questionScoreId);
answerCheckMark = itemView.findViewById(R.id.answerCheckMarkId);
answerAuthorTextView = itemView.findViewById(R.id.answerAuthorTextViewId);
answerTimeStampTextView = itemView.findViewById(R.id.answerTimeStampTextViewId);
mAnswerUpVoteButton = itemView.findViewById(R.id.answerUpVoteId);
mAnswerDownVoteButton = itemView.findViewById(R.id.answerDownVoteId);
mContext = itemView.getContext();
}
}
private void answerUpVote(#NonNull final AnswerAdapter.AnswerHolder answerHolder, final int position, #NonNull final Answer model) {
final String answerScoreFBValue = getSnapshots().getSnapshot(position).get("answerScore").toString();
final String answerFirebaseIdString = getSnapshots().getSnapshot(position).get("answerFirebaseId").toString();
// Toast.makeText(mContext, "upvote button clicked " + answerScoreFBValue, Toast.LENGTH_SHORT).show();
final CollectionReference answerCollectionRef = FirebaseFirestore.getInstance().collection("Answers");
final DocumentReference answerDocRef = answerCollectionRef.document(answerFirebaseIdString);
answerDocRef.update("answerScore", FieldValue.increment(1)).addOnSuccessListener(new OnSuccessListener<Void>() {
// answerRef.document().update("answerscore", answerScoreTestInt).addOnSuccessListener(new OnSuccessListener<Void>() {
#Override
public void onSuccess(Void aVoid) {
Log.d(TAG, "onSuccess: answerScore incremented");
// answerHolder.answerScoreTextView.setText("testing");
}
}).addOnFailureListener(new OnFailureListener() {
#Override
public void onFailure(#NonNull Exception e) {
Log.e(TAG, "onFailure: answerScore was not incremented", e);
}
});
}
Answer.java
package com.example.stairmaster.models;
public class Answer {
private String answer;
private String comment;
private String timestamp;
private int answerScore;
private String author;
private String answerFirebaseId;
private String parentQuestionId;
public Answer(String answer, String timeStamp, String author, String parentQuestionId, int answerScore) {
this.answer = answer;
this.timestamp = timeStamp;
this.answerScore = answerScore;
this.author = author;
this.parentQuestionId = parentQuestionId;
}
public Answer () {
// public no-arg constructor needed
}
public String getAnswerFirebaseId() {
return answerFirebaseId;
}
public void setAnswerFirebaseId(String answerFirebaseId) {
this.answerFirebaseId = answerFirebaseId;
}
public String getAnswer() {
return answer;
}
public void setAnswer(String answer) {
this.answer = answer;
}
public String getComment() {
return comment;
}
public void setComment(String comment) {
this.comment = comment;
}
public String getTimestamp() {
return timestamp;
}
public void setTimestamp(String timestamp) {
this.timestamp = timestamp;
}
public int getAnswerScore() {
return answerScore;
}
public void setAnswerScore(int answerScore) {
answerScore = answerScore;
}
public String getAuthor() {
return author;
}
public void setAuthor(String author) {
this.author = author;
}
public String getParentQuestionId() {
return parentQuestionId;
}
public void setParentQuestionId(String parentQuestionId) {
this.parentQuestionId = parentQuestionId;
}
}

You might consider modifying your database tables in your firebase database to look like the following.
user_votes:
- userid: 1234232 // A sample user id for your user
- forumid: 8769765 // The id of the forum or blog
- upvote: 1 // Number of upvotes in that specific forum/blog
- downvote: 0 // Number indicates the downvote to that forum/blog
- forumid: 1233432
- upvote: 1
- downvote: 0
- forumid: 8712169767
- upvote: 0
- downvote: 1
And the other table which keeps the record of upvotes/downvotes for a specific forum/blog might look like the following.
all_votes:
- forumid: 8769765 // The id of the forum or blog
- upvote: 9876 // Number of upvotes in that specific forum/blog
- downvote: 123 // Number indicates the downvote to that forum/blog
- forumid: 1233432
- upvote: 87
- downvote: 12
- forumid: 8712169767
- upvote: 112
- downvote: 10
Now, based on these two tables, you can easily decide if you are going to issue a setValue command in your all_votes table for a forum id. If the user is found to have issued an upvote already, then make the application logic in such a way that it does not issue another upvote in the all_votes table for that specific user.
Hope that helps!

I am working on a similar thing and the way I did it was using a local room database. So first, it checks the local database to see if the user can vote. If they can, then it sends a request to firestore to update the associated field. I am also using RxKotlin and wrapping the returned Task<> (from the request to firestore) into an observable. If the observable completes without any error, then it updates the local database. If it fails, then it doesn't do anything. I am sure there are other methods out there but this is how I chose to do it.

Related

Modeling circular dependencies while maintaining data integrity

I'm designing a music information system. I have a couple of entities that are connected to each other.
Below is part of the domain code.
class Album {
private Set<Track> tracks;
private boolean published;
public Set<Track> getTracks() {
return this.tracks;
}
public boolean isPublished() {
return this.published;
}
public void publish() {
System.out.println("Album.publish() called");
this.published = true;
this.tracks.forEach(track -> track.publish());
}
}
class Track {
private boolean published;
private Album album;
public boolean isPublished() {
return this.published;
}
public Album getAlbum() {
return this.album;
}
public void publish() {
// if track is single (this.album == null), set published to true
// if track is part of an album and the album is NOT published, return;
// if track is part of an album and the album is published, set published to true
if(this.album != null && !this.album.isPublished())
return;
this.published = true;
}
}
Track is an independent entity. It can be a single track (I.e. without an Album). So the album attribute is actually needed.
One domain rule is that when an album is archived (i.e. not published), its tracks cannot be published neither and if an album is published, any of its tracks can either be published or archived.
The problem is that when an album is published (e.g. album1.publish()), its tracks' publish() method is called as well. But track1.publish() checks if the album is published based on the copy it already has (which is not published).
How can I solve the problem?
If you split domain model entities by behaviour, you can get rid of described limitations
Let's have some interfaces for such entities:
interface AlbumId{
String asString();
AlbumId Absent = () -> "NO ALBUM AT ALL";
}
interface Publication{
void publish() throws Exception;
void archive() throws Exception;
boolean published();
}
interface Track{
TrackId id();
AlbumId albumId(); //smart type (as DDD suggest), therefore, no more nulls
}
Now you may enforce rules by creating class that will get you a list of tracks you can publish:
public class TracksReadyToPublishOf implements Supplier<Map<TrackId, TrackPublication>>{
//this class may access to cache and have dosens of other optimizations
public TracksReadyToPublishOf(AlbumId id){...}
#Override public get(){...}
}
Then you can reuse your code to check your rules anywhere:
public class TrackPublication implements Publication {
private final Track track;
private final Supplier<Map<TrackId, TrackPublication>> allowedTracks;
//easy for unit testing
public SmartTrackPublication(Track track, Supplier<Map<TrackId, TrackPublication>> allowedTracks){
this.track = track;
this.allowedTracks = allowedTracks;
}
public SmartTrackPublication(Track track){
this(track, new TracksReadyToPublishOf(track.albumId());
}
#Override
public publish() throws AlbumArchivedException{
if(this.albumId != AlbumId.Absent){
if(!this.allowedTracks.get().containsKey(this.track.id())){
throw new AlbumArchivedException();
}
}
this.allowedTracks.get().get(this.id()).publish();
}
}
And for album publishing:
public class AlbumPublication implements Publication{
private final AlbumId id;
private final Producer<Map<TrackId, TrackPublication>> tracks
private AlbumWithTracks(AlbumId id, Producer<Map<TrackId, TrackPublication>> tracks){
this.id = id;
this.tracks = tracks;
}
public AlbumWithTracks(AlbumId id){
this(id, new TracksReadyToPublishOf(id))
}
...
#Override publish() throws Exception{
//code for publishing album
for(TrackPublication t : Arrays.asList(
this.tracks.get()
)){
t.publish(); //track can publish anyway if it presents in list above
}
}
}

How not to overwrite RecyclerView list when adding an item in Room Database

I recently started programming in android and I have a problem about RecyclerView and LiveData:
when I delete or add an item in my RoomDatabase, LiveData changes correctly and my observer starts. I have items that can be checked but, when I delete or add an item in RecyclerView, they are overwritten and I lose every checked item. I've tried to use DiffUtil but I think that I've sadly misunderstood its usage.
I've seen every tutorial on yt, medium and StackOverflow but each one overwrites the data. isn't it ineffective when I have n complex-items?
Any suggestions about how not to lose checked information?
I don't want to save checked information in roomDatabase because when I restart the application there must be no checked items.
Extract of myPlace class and Room Entity:
#Entity(tableName = "myPlaces")
public class MyPlace {
#PrimaryKey
#NonNull
private String place_id;
private String name;
private String reference;
#TypeConverters(GeometryConverter.class)
#ColumnInfo(name = "geometry")
#SerializedName("geometry")
private Geometry geometry;
private String vicinity;
#Ignore
private boolean checked;
#Ignore
private Bitmap image;
...
}
LiveData Observer:
...
LiveData<List<MyPlace>> liveData = favoriteViewModel.getFavoritePlaces();
liveData.observe(this, myPlaces -> {
recyclerView.setAdapter(myFavoritePlacesAdapter);
myFavoritePlacesAdapter.setFavoritePlaces(new ArrayList<>(myPlaces));
...
};
In my Adapter:
#Override
public void onBindViewHolder(#NonNull final MyFavoritePlaceViewHolder holder,final int position) {
final MyPlace myPlace = favoritePlaces.get(holder.getAdapterPosition());
holder.bind(myPlace);
holder.place.setOnClickListener(v -> {
if((listener != null)){
holder.place.setChecked(myPlace.isChecked());
listener.onItemClick(holder.getAdapterPosition());
}
});
holder.place.setOnLongClickListener(v -> {
if (listener != null) {
myPlace.setChecked(!myPlace.isChecked());
holder.place.setChecked(myPlace.isChecked());
listener.onItemLongClick(position);
}
return true;
});

How do I update a field in a room database using a repository & viewmodel

I created a room database following this guide from code labs It makes use of a repository to:
A Repository manages query threads and allows you to use multiple backends. In the most common example, the Repository implements the logic for deciding whether to fetch data from a network or use results cached in a local database.
I followed the guide and i'm now able to create the entity's & retrieve the data. I even went further and created another whole entity outside the scope of the guide.
However I can't find many resources that use this MVVM(?) style so am struggling as to really under stand the repository. For now I want to update a field. Just one, as if I am able to manage that the rest should be similar.
I want to update a field called dartshit and I have the dao method created for this:
#Query("UPDATE AtcUserStats SET dartsHit = :amount WHERE userName = :userName")
void UpdateHitAmount(int amount, String userName);
I have one repository which I assumed I use for all entities:
public class UsersRepository {
private UsersDao mUsersDao;
private AtcDao mAtcDao;
private LiveData<List<Users>> mAllUsers;
private LiveData<List<AtcUserStats>> mAllAtc;
private AtcUserStats mAtcUser;
UsersRepository(Application application) {
AppDatabase db = AppDatabase.getDatabase(application);
mUsersDao = db.usersDao();
mAtcDao = db.atcDao();
mAllUsers = mUsersDao.fetchAllUsers();
mAllAtc = mAtcDao.getAllAtcStats();
}
LiveData<List<Users>> getAllUsers() {
return mAllUsers;
}
LiveData<List<AtcUserStats>> getAllAtcStats() {
return mAllAtc;
}
LiveData<AtcUserStats> getAtcUser(String username) {
return mAtcDao.findByName(username);
}
public void insert (Users user) {
new insertAsyncTask(mUsersDao).execute(user);
}
public void insertAtc (AtcUserStats atc) {
new insertAsyncAtcTask(mAtcDao).execute(atc);
}
private static class insertAsyncTask extends AsyncTask<Users, Void, Void> {
private UsersDao mAsyncTaskDao;
insertAsyncTask(UsersDao dao) {
mAsyncTaskDao = dao;
}
#Override
protected Void doInBackground(final Users... params) {
mAsyncTaskDao.insertNewUser(params[0]);
return null;
}
}
private static class insertAsyncAtcTask extends AsyncTask<AtcUserStats, Void, Void> {
private AtcDao mAsyncTaskDao;
insertAsyncAtcTask(AtcDao dao) {
mAsyncTaskDao = dao;
}
#Override
protected Void doInBackground(final AtcUserStats... params) {
mAsyncTaskDao.insertNewAtcUser(params[0]);
return null;
}
}
}
My question is how do I create a AsyncTask for the update query I am trying to run in this repository?
Here is what I have so far by broadly copying the insert repository methods:
private class updateHitAsyncTask {
private AtcDao mAsyncTaskDao;
public updateHitAsyncTask(AtcDao mAtcDao) {
mAsyncTaskDao = mAtcDao;
}
protected Void doInBackground(int amount, String name) {
mAsyncTaskDao.UpdateHitAmount(amount, name);
return null;
}
}
Which is incorrect is that I'm getting a llegalStateException: Cannot access database on the main thread since it may potentially lock the UI for a long period of time. error. But i thought this AsyncTask is suppose to take care of this?
Here is my update method in my view model, which is reporting 0 errors:
void updateHitAmount (int amount, String name) {
mRepository.updateAtcHits(amount, name);
}
and here is the UI code where im actually trying to tie all these together, I suspect there must be a better way that using onChanged for simply updating a field but again I am struggling to come across any advice on google with the repository approach:
private void callOnChanged() {
mAtcViewModel = ViewModelProviders.of(this).get(AtcViewModel.class);
mAtcViewModel.getAllUsers().observe(this, new Observer<List<AtcUserStats>>() {
#Override
public void onChanged(#Nullable final List<AtcUserStats> atc) {
// Update the cached copy of the users in the adapter.
for (int i = 0; i < atc.size(); i++) {
if (atc.get(i).getUserName().equals(mUser)) {
mAtcViewModel.updateHitAmount(55, mUser);
//atc.get(i).setDartsHit(55);
Log.d("id", String.valueOf(userSelected.getId()));
}
}
}
});
How can I update fields using this approach on the background thread?
Figured it out due to this answer here. It was mostly because of my lack of understanding of AsyncTask. Essentially I needed to create an object and pass the data that way and then execute in the background:
private static class MyTaskParams {
int amount;
String name;
MyTaskParams(int amount, String name) {
this.amount = amount;
this.name = name;
}
}
public void updateAtcHits (int amount, String name) {
MyTaskParams params = new MyTaskParams(amount,name);
new updateHitAsyncTask(mAtcDao).execute(params);
}
private class updateHitAsyncTask extends AsyncTask<MyTaskParams,Void,Void>{
private AtcDao mAsyncTaskDao;
public updateHitAsyncTask(AtcDao mAtcDao) {
mAsyncTaskDao = mAtcDao;
}
#Override
protected Void doInBackground(MyTaskParams... myTaskParams) {
int amount =myTaskParams[0].amount;
String name = myTaskParams[0].name;
mAsyncTaskDao.UpdateHitAmount(amount, name);
return null;
}
}

Espresso: Getting a text value from a textview and storing in a String?

I wanted to assert a part of the 'text' i get from a textview and later store it in a string, but not sure how i am going to do this.
Following is the code snippet for reference :
private void validateFlightOverviewWidgetDate(int resId, String value, boolean outBound) throws Throwable {
if (ProductFlavorFeatureConfiguration.getInstance().getDefaultPOS() == PointOfSaleId.UNITED_STATES) {
onView(allOf(outBound ? isDescendantOfA(withId(R.id.package_outbound_flight_widget))
: isDescendantOfA(withId(R.id.package_inbound_flight_widget)),
withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE),
withId(resId)))
.check(matches(withText(containsString("Dec 22 "))));
I want to store the value of "Dec 22" in a string so that later i can use it for assertion.
You may have to create a custom ViewAction to help you to get text from TextView:
public class GetTextAction implements ViewAction {
private CharSequence text;
#Override public Matcher<View> getConstraints() {
return isAssignableFrom(TextView.class);
}
#Override public String getDescription() {
return "get text";
}
#Override public void perform(UiController uiController, View view) {
TextView textView = (TextView) view;
text = textView.getText();
}
#Nullable
public CharSequence getText() {
return text;
}
}
Then you can get text by:
GetTextAction action = new GetTextAction();
onView(allOf(isDescendantOf(...), withId(...), withEffectiveVisibility(...)))
.perform(action);
CharSequence text = action.getText();
Though I'd not recommend to use this way for test assertion, it seems unconventional and awkward. Also, you don't really need to have isDescendantOf(...) in your allOf combination because of withId, unless the id is not unique.

Reading an object from a Firebase database in android studio

Using Android Studio and Firebase, i'm trying to write and read some data.
I have a Pub Class which contains the folowing:
package com.example.jef.pubbuddy.Database;
import java.util.ArrayList;
public class Pub {
private String name;
private float longitude;
private float lattitude;
private ArrayList<Pub> Pubs = new ArrayList<>();
public Pub() {}
public void setName(String name)
{this.name = name;}
public void setLongitude(float longitude)
{this.longitude = longitude;}
public void setLatitude(float lattitude)
{this.lattitude = lattitude;}
public String getName()
{return name;}
public float getLatitude()
{return lattitude;}
public float getLongitude()
{return longitude;}
I write my Pub object to the database using the .push() method. Below is how i write it to the database. It appears just fine in the Firebase console, so I believe the problem doesn't lie here:
Pub p1 = new Pub();
p1.setName("The name of the pub");
p1.setLatitude((float) 4.699545);
p1.setLongitude((float) 50.878267);
myRef.child("PUSH_TEST").push().setValue(p1);
Afterwards I try to read it using the following code. Please note the message method is just used to append some information to a TextView, so i'm able to debug on my physical device. However, none of the listener events get triggered.
Does anyone knows what i'm doing wrong here? Already followed the official firebase documentation and the "Firebase in a weekend" training videos. Also looked up countless answers here on Stackoverflow, but I can't seem to make it work.
Thanks in advance.
public class Database extends AppCompatActivity {
private TextView tv;
int messages;
private ArrayList<Pub> pubList = new ArrayList();
private FirebaseDatabase database;
private DatabaseReference myRef;
#Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_database);
database = FirebaseDatabase.getInstance();
myRef = database.getReference();
init();
writeData();
message("creating and attaching the listener");
ChildEventListener myListener = new ChildEventListener() {
#Override
public void onChildAdded(DataSnapshot dataSnapshot, String s)
{
message("The childEvent triggered");
Pub p = dataSnapshot.getValue(Pub.class);
message("The name of this pub = " + p.getName());
pubList.add(p);
}
#Override
public void onChildChanged(DataSnapshot dataSnapshot, String s) {}
#Override
public void onChildRemoved(DataSnapshot dataSnapshot) {}
#Override
public void onChildMoved(DataSnapshot dataSnapshot, String s) {}
#Override
public void onCancelled(DatabaseError databaseError) {}
};
myRef.child("PUSHTEST").addChildEventListener(myListener);
}
Everything is correct, except this:
Here you set the value:
myRef.child("PUSH_TEST").push().setValue(p1);
and here you retrieve the value:
myRef.child("PUSHTEST").addChildEventListener(myListener);
the child that you wrote is wrong as it is not in your database. So just change it into this:
myRef.child("PUSH_TEST").addChildEventListener(myListener);
the name inside child(..) needs to be the same as in your database
You write data to "PUSH_TEST" child and trying to read from "PUSHTEST". Make it same.
For not getting similar errors in future, create a class called "Constants.java" and add constant strings inside it. Like,
public class Constants {
public static final String CHILD_NODE="PUSH_TEST";
}
So that , you can use this constant, where ever u need. Just call Constants.CHILD_NODE. So there will not be such errors.

Categories

Resources