Firebase upvote adding id which doesn't exist [duplicate] - java

This question already has answers here:
How to save users score in firebase and retrieve it in real-time in Android studio
(3 answers)
Closed 4 years ago.
I'm having a problem when I try to star a movie which id doesn't exist in my likes firebase node. Here's my code:
private void onStarClicked(long releaseId, final String uid) {
final DatabaseReference postRef = ((GamePageActivity)getActivity()).mDatabaseRef.child("likes").child(mRegion).child(String.valueOf(releaseId));
postRef.runTransaction(new Transaction.Handler() {
#Override
public Transaction.Result doTransaction(MutableData mutableData) {
_Post p = mutableData.getValue(_Post.class);
if (p == null) {
Log.d(TAG, "Transaction success");
return Transaction.success(mutableData);
}
if (p.stars.containsKey(uid)) {
// Unstar the post and remove self from stars
p.starCount = p.starCount - 1;
p.stars.remove(uid);
} else {
// Star the post and add self to stars
p.starCount = p.starCount + 1;
p.stars.put(uid, true);
}
// Set value and report transaction success
mutableData.setValue(p);
return Transaction.success(mutableData);
}
#Override
public void onComplete(DatabaseError databaseError, boolean b,
DataSnapshot dataSnapshot) {
// Transaction completed
Log.d(TAG, "postTransaction:onComplete:" + databaseError);
}
});
}
The problem is if my id "releaseId" doesn't exist in the database, the star won't get added in, I thought my code was supposed to first add the "releaseId" if it doesn't exist?

It seems a bit complicated to try to add a movie inside your likes path during a ‘like’ transaction. Ideally, it would already be there. So it will be easier to separate adding the movie to the likes path, and after that use a Cloud Function onCreate or onUpdate.
I assume you have your movie data somewhere else in your database. Let’s call it the ‘movies’ path for now. One idea to try is to have a Cloud Function that watches your ‘movies’ path, and when a movie is added, the function will add that movie data to your ‘likes’ path. That way, once a user tries to like a movie, its information is already in the correct spot in the database. Fixing the problem of the non-existent 'releaseId'.
Firebase Realtime Database Triggers
If you don’t want to use a Cloud Function, you can consider writing to both the ‘movies’ path and ‘likes’ path directly from the client when the movie is added, to accomplish the same thing.

Related

Adding an Input Value to an Existing Firebase Database Value Java?

I have an activity that registers active time data to my Firebase Realtime Database, this is input manually using an EditText which then displays it to a TextView in the UI. This is all working fine, however, if I was to put more data in, it would simply replace the value in the database and TextView.
I have previously done something similar to this in another activity (Adding (Sum) multiple Firebase child values together to give a total value?), however, this activity had additional nodes with dates, etc so the same approach would not work in my current activity. Ideally, I would have this node organized with dateStrings etc, however, for demonstration purposes, it's not necessary. Therefore I would just like to take whatever value is already existing in the database and add to it the input value, then restore that to the database.
It's probably quite simple but I've been staring at it so long I've confused myself. This is nowhere near right but just a bit confused about my next steps.. I've seen perhaps something along these lines how to sum money value in firebase realtime android studio but wondering if this is the easiest way?
Register Activity method:
registerActivity.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
String activityTime = activeMinutes.getText().toString().trim();
databaseReference.child("active time").setValue(activityTime);
addActivity.setVisibility(View.VISIBLE);
registerActivity.setVisibility(View.GONE);
activeTimeTI.setVisibility(View.GONE);
}
});
Method to display data (shortened):
databaseReference.addValueEventListener(new ValueEventListener() {
#RequiresApi(api = Build.VERSION_CODES.O)
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
int total = 0;
String activityTime = dataSnapshot.child("active time").getValue(String.class);
if (activityTime == null) {
activeTimeTV.setText("0");
} else {
total += Integer.parseInt(activityTime);
activeTimeTV.setText(String.valueOf(total) + " minutes");
}
}
#Override
public void onCancelled(DatabaseError databaseError) {
Log.w("TAG", "onCancelled", databaseError.toException());
}
});
Firebase Hierarchy:
According to your last comment:
Active Time is at 500 (mins) just now, if I did another 90 mins of activity, I would like to add 90 to that value and then update that to show 590.
To increment the value of "active time" by 90, first of all, you need the change the type of the field to be number. That being said, your field should look like this:
ndkdn
|
--- active time: 500
See, there are no quotation marks. If your "ndkdn" node is a direct child of your Firebase Realtime Database root, to increase the value by 90, simply use the following lines of code:
DatabaseReference rootRef = FirebaseDatabase.getInstance().getReference();
DatabaseReference activeTimeRef = rootRef.child("ndkdn").child("active time");
activeTimeRef.setValue(ServerValue.increment(90));
This kind of operation is atomic. If you are not allowed to change the type of your field, to also have consistent data, then you should read the value first and increment it right after that using a transaction:
activeTimeRef.runTransaction(new Transaction.Handler() {
#Override
public Transaction.Result doTransaction(MutableData mutableData) {
Integer activeTime = Integer.parseInt(mutableData.getValue(String.class));
if (score == null) {
return Transaction.success(mutableData);
}
mutableData.setValue((activeTime + 90) + "");
return Transaction.success(mutableData);
}
#Override
public void onComplete(DatabaseError error, boolean b, DataSnapshot snapshot) {
Log.d("TAG", error.getMessage()); //Don't ignore potential errors!
}
});

How can I perform OR query while searching in firebase?

Here are the various attributes of a person.
I want to implement a search where the results come if any of the fields: specializationField, hospitalName or fullName have the same letters.
For example if I search 'sh', this person should appear in the field, because of the similar hospital name.
This is the code I am using to search only for fullName:
FirebaseRecyclerOptions<DoctorHelperClass> options =
new FirebaseRecyclerOptions.Builder<DoctorHelperClass>()
.setQuery(FirebaseDatabase.getInstance().getReference().child("Doctor").orderByChild("fullName").startAt(s.toUpperCase()).endAt(s.toLowerCase()+"\uf8ff"), DoctorHelperClass.class)
.build();
adapter = new DoctorsAdapters(options, FindDoctorActivity.this);
adapter.startListening();
binding.rvListDoctors.setAdapter(adapter);
Please help me out
As #Puf said, you can't achieve it at Firebase Realtime Database but you can do it at client side which mean at the Android part.
First, you cannot use FirebaseUI which is you are currently using, instead you need to use https://firebase.google.com/docs/database/android/read-and-write#read_data
ValueEventListener postListener = new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
// You have to make for each loop
for (DataSnapshot snapshot : dataSnapshot.getChildren()) {
DoctorHelperClass doc = snapshot.getValue(DoctorHelperClass.class);
//List them in an array
docList.add(doc);
}
}
#Override
public void onCancelled(DatabaseError databaseError) {
// Getting Post failed, log a message
Log.w(TAG, "loadPost:onCancelled", databaseError.toException());
}
};
mPostReference.addValueEventListener(postListener);
Once you have added all the list of doctors. You can compare them using the arrayList.
You can do something like this.
private void searchDoc(final String inputDoc){
boolean isFound = false;
for (DoctorHelperClass doc in docList){
if (doc.getFullName() == inputDoc && doc.getHospitalName() == inputDoc){
isFound = true;
//Do something if found
}
}
}
I hope you get the concept of it.
There is no support for OR conditions in Firebase Realtime Database. You will either have to perform multiple queries and merge the results client-side, or create a specialized field for performing this search.
But given your question, you may be looking for text search capabilities that are well beyond what Firebase Realtime Database handles. Instead of trying to shoehorn those requirements onto Firebase, I recommend using an additional (or even other) database for meeting your text search requirements.
Also see:
Use firebase realtime database create search function
How to search anywhere in string in Firebase Database - Android
Searching in Firebase without server side code
Firebase and indexing/search

Issue with Firebase onChildRemoved() and removeValue() working on time sensitive operation

I have an application that offers some offers for some stuff. The idea is the offer is added by me on another application I created and then be shown on users devices all at the same time.
I need only one user to be able to take the offer so when the first user clicks I do call removeValue() on the offer ref. The offer is correctly deleted from the database and from the other users recyclerview.
The problem is when 2 clicks happens in the same time the offer is deleted but onChildRemoved() doesn't have the time to be called so both users now have the same offer!
Is there any other idea how to make this operation more precise and time aware?
UPDATE as suggested from svi.data i tried this piece of code on user click but still the same problem occur.
offerUnAnsweredRef.addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(#NonNull DataSnapshot dataSnapshot) {
boolean stillThere = false;
for (DataSnapshot offerSnap : dataSnapshot.getChildren()) {
if (offerSnap.getKey().equals(requestedOffer.getCurrentNodeKey())) {
stillThere = true;
}
}
if (stillThere) {
Timber.d("We have it " + requestedOffer.getEmployeeKey());
Toast.makeText(getContext(), "Welcome Dear ", Toast.LENGTH_SHORT).show();
offerUnAnsweredRef.child(requestedOffer.getCurrentNodeKey()).removeValue();
} else {
Toast.makeText(getContext(), "Go Away Bear", Toast.LENGTH_SHORT).show();
}
}
#Override
public void onCancelled(#NonNull DatabaseError databaseError) {
}
});
UPDATE 2
Actually the solution is built on top of svi.data answer with some modifications so i wanted to share the working code to help if any one ever come cross similar situation
offerUnAnsweredRef.child(requestedOffer.getCurrentNodeKey()).runTransaction(new Transaction.Handler() {
#NonNull
#Override
public Transaction.Result doTransaction(#NonNull MutableData mutableData) {
RequestedOffer o = mutableData.getValue(RequestedOffer.class);
if (o == null) {
return Transaction.abort();
}
if (o.getEmployeeKey() == null) {
o.setEmployeeKey(employee.getUid());
mutableData.setValue(o);
return Transaction.success(mutableData);
} else {
return Transaction.success(mutableData);
}
}
#Override
public void onComplete(DatabaseError databaseError, boolean b,
DataSnapshot dataSnapshot) {
// check if the transaction completed successfully
// or if it failed
RequestedOffer o = dataSnapshot.getValue(RequestedOffer.class);
if (o.getEmployeeKey() == employee.getUid()) {
getActivity().runOnUiThread(() -> Toast.makeText(getActivity(), "Hello", Toast.LENGTH_SHORT).show());
DatabaseReference databaseReference = FirebaseFactory.getDatabase()
} else {
getActivity().runOnUiThread(() -> Toast.makeText(getActivity(), "NO", Toast.LENGTH_SHORT).show());
}
}
because as documentation says
public abstract Transaction.Result doTransaction (MutableData currentData)
This method will be called, possibly multiple times, with the current data at this location. It is responsible for inspecting that data and returning a Transaction.Result specifying either the desired new data at the location or that the transaction should be aborted.
So i added the code to check in onComplete to ensure that it called only once.
From what I understand:
1) You have a specific app for adding the offers (by you).
2) You have another app for reading the offers (by users).
3) If this is the case then both apps use the same project.
4) When a user clicks an offer, he/she will get the offer, then you will delete the offer from the database.
5) Now when 2 users click the same offer there is no time for the offer to be removed from the other user's list, so they end up with the same offer.
Now it seems that you don't want users to get same offers, and the problem really is a timing issue.
Possible solution:
1) When ever a user clicks an offer, you run a ValueEventListener() to the offers node in the database and check if the offer exist.
2) If the offer exists give him/her the offer and delete it.
3) Now when 2 users click the same offer, the ValueEventListener that I talked about will provide you with some time before reacting.
4) So users shouldn't end up with same offers.
Hope it solves your problem.
UPDATE:
As this is is a race condition between users, its time to talk about transactions. Firebase provides a nice way to directly read and write concurrent updates to the same node (which is your case).
I want your database to be like this:
Offers
|
|------offer_id_1
|
|-----taken:false
|-----......
|-----......
|
|-------offer_id_2
|
|------taken:false
|------......
|------......
Let me explain the above structure, each offer you post from the other application will have a flag by default called taken and it should have by default a value of false.
Now as you see above offer_id_1 and offer_id_2 are the push id or the random id given for the offer (when a user click on an offer you must get a reference of this key....I assume you know how to do this).
Before we start ofcourse you should have a model class for your posts we will call it Offer its just a class:
public class Offer{
public boolean taken;
......
......
}
The below function is what you will call after someone clicked an offer (we will use a transaction):
public void RunTransactionFor(String offer_id){
//first refer to your offers
DatabaseReference offers_ref = FirebaseDatabase.getInstance().getReference().child("offers").child(offer_id);
//run a transaction (a transaction is fast it reads and writes directly)
offer_ref.runTransaction(new Transaction.Handler() {
#Override
public Transaction.Result doTransaction(MutableData mutableData) {
//this is a ref to the offer class
Offer offer = mutableData.getValue(Offer.class);
if (offer == null) {
return Transaction.success(mutableData);
}
if(offer.taken == false){
//take the offer
offer.taken = true;
//show a message
Toast.makeText(context, "you took the offer",...).show();
//now you can remove the offer
offers_ref.setValue(null);//or delete it your way
}else{
//too late the offer is taken
Toast.makeText(context, "too late the offer is gone",...).show();
//do nothing
}
// Set value and report transaction success
mutableData.setValue(offer);
return Transaction.success(mutableData);
}
#Override
public void onComplete(DatabaseError databaseError, boolean b,
DataSnapshot dataSnapshot) {
// Transaction completed
}
});
}
Now when a user clicks an offer in the list store the id of the offer and pass it to the above function like this
//after user clicks
String offer_id = .......
//run transaction
RunTransactionFor(offer_id);
Note: Transactions only work online they can't work offline.

How can I check if the data is present in firebase database only one time without event listeners [duplicate]

This question already has answers here:
Checking if a particular value exists in the Firebase database
(6 answers)
Closed 4 years ago.
I want to check if the specific user is present in database or not and if it is present then I don't want to override the data in the database.
I am using unique key generation at every node provided by firebase as User Id's
So I don't want to duplicate the user if he is already present.
How can I check if this user is present in database without listening to any events because my program execution is dependent on this values
When I am doing this the execution goes in a infinite loop
USER_REFERENCE.addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
Iterable<DataSnapshot> children = dataSnapshot.getChildren();
for (DataSnapshot d : children) {
if (d.child("email").exists()) {
if (d.child("email").getValue().equals(user.getEmail())) {
user.setUserId(d.getKey());
mPresenter.saveUserIntoSession(user);
} else {
String userId = addUser(user);
user.setUserId(userId);
mPresenter.saveUserIntoSession(user);
}
}
}
}
Try this to check:
DatabaseReference ref=FirebaseDatabase.getInstance().getReference().child("Users");
ref.orderByChild("username").equalTo(name).addListenerForSingleValueEvent(new ValueEventListener(){
#Override
public void onDataChange(DataSnapshot dataSnapshot){
if(dataSnapshot.exist() {
//username exist
}
}
The above will read data once and it will check if a particular username exists in the database
Use addListenerForSingleValueEvent to read data once.
Restating the answer in simpler terms:
Given:
#Override
public boolean onLongClick(View view) {
return true; // or false
}
return true means that the event is consumed. It is handled. No other click events will be notified.
return false means the event is not consumed. Any other click events will continue to receive notifications.
So if you don't want onClick to also be triggered after an onLongClick, then you should return true from the onLongClick event.

Parse powered by Bitnami for Android (friends list)

So I have written an on click method that takes in text from a user (a friend's name) then checks to see if that user exists on the database and if you are already friends with that user. If the user exists and isn't already your friend, I want it to add them to your friends list, which is an array on the Parse backend. The checks seem to be working, and "frank" is added to the list on the device however the list isn't being updated or saved on the server and I can't work out why, I've checked variable and database names for error and I cant find any. I'm testing logged in as "bill". Please find method and screenshot of database below. Any help would be greatly appreciated.
Parse bitnami database here
public void addFriend(View view){
final EditText mText = (EditText)findViewById(R.id.editText);
ParseQuery<ParseUser> query = ParseUser.getQuery();
query.whereEqualTo("username", mText.getText().toString());
query.countInBackground(new CountCallback() {
#Override
public void done(int count, ParseException e) {
if (e == null) {
if(count==0){
Toast.makeText(getApplicationContext(), "User Doesn't Exist", Toast.LENGTH_LONG).show();
}
else if (ParseUser.getCurrentUser().getList("friendsList").contains(mText.getText().toString()))
{Toast.makeText(getApplicationContext(), "User is already a friend", Toast.LENGTH_LONG).show();}
else
{
ParseUser.getCurrentUser().getList("friendsList").add(mText.getText().toString());
ParseUser.getCurrentUser().saveInBackground();
}
}
}
});
}
I haven't used Parse much myself, but from the API documents here: http://docs.parseplatform.org/android/guide/#arrays
It looks like what is happening is you are using the .add() method from List<>, but it seems you need to use the Parse's specific .add() method on the ParseObject.
Try changing this line:
ParseUser.getCurrentUser().getList("friendsList").add(mText.getText().toString());
to
ParseUser.getCurrentUser().add("friendsList", mText.getText().toString());

Categories