I have a RecyclerView which is populated by posts stored in a Firestore database.
Each post is written as a document with a unique postID, storing the posted message, a timestamp and a like-counter.
//mUploads is defined as private List<Upload> mUploads;
//Upload object stores post message, timestamp and likes
mUploads = new ArrayList<>();
mFireStoreOrdered = mFireStoreInst.collection("posts").orderBy("time");
mFireStoreOrdered
.get()
.addOnCompleteListener(new OnCompleteListener<QuerySnapshot>() {
#Override
public void onComplete(#NonNull Task<QuerySnapshot> task) {
if (task.isSuccessful()) {
for (DocumentSnapshot doc : task.getResult()) {
//For each document get the ID
String postID = doc.getId();
// Upload object stores post message, timestamp and likes
Upload upload = doc.toObject(Upload.class).withId(postID);
mUploads.add(upload);
}
Collections.reverse(mUploads);
//Populate Recyclerview
mAdapter = new UploadAdapter(MainActivity.this, mUploads);
mContentView.setAdapter(mAdapter);
} else {
//...
}
}
});
When trying to implement the "like"-functionality for these posts I got to the limits of Firestore, which can only handle one document update per second.
Reading this article convinced me of using the Firebase Realtime Database to store the likes by using transaction operations instead of using distributed counters. I do not want to display the likes in real-time, I only want to use the RTDB to handle multiple likes/dislikes per second.
When additionally using the Firebase RTDB for likes, I would add data to a path /posts/postID/likes.
How can I get the post messages from Firestore and add the corresponding likes from the RTDB to mUploads before passing it to the adapter. Specificially, is it possible to ensure that I set the correct like value to its corresponding post, without querying for each postID.
This is a very common practice when it comes to Firestore, to store the number of likes in the Firebase Realtime database, otherwise you'll be charged for every read/write operation as explained in my answer from this post. So using Firebase Realtime database you can host the number of likes at no cost.
So, how can be done? First of all, you are guessing right. The number of likes should be added beneath the postId like this:
Firebase-root
|
--- likes
|
--- postIdOne: numberOfLikes //must be stored as an integer
|
--- postIdOTwo: numberOfLikes //must be stored as an integer
|
--- //and so on
To achive what you want, you need to follow the next steps.
Every time you you add a new post, add the corresponding post id in Firebase Realtime database like above by setting the value of that particular post id to 0.
Every time you get a new like increase the value of that postId by one. Every time a user retracts a like, decrease the value of that postId by one. To achieve this and also to have consistent data, I recommend you use Firebase Transactions.
Then in your adapter class, where you are displaying data from Firestore, when you want to set the number of likes to a view, just attach a listener on that particular post id node and get the number of likes. Inside the onDataChange() set that number to a TextView like this:
DatabaseReference rootRef = FirebaseDatabase.getInstance().getReference();
DatabaseReference noOfLikesRef = rootRef.child("likes").child(postId);
ValueEventListener valueEventListener = new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
String numberOfLikes = "(" + dataSnapshot.getValue() + ")";
numberOfLikesTextView.setText(numberOfLikes);
}
#Override
public void onCancelled(DatabaseError databaseError) {}
};
noOfLikesRef.addListenerForSingleValueEvent(valueEventListener);
That's it!
Related
Basically what I am trying to do is I have a database with the name users having an attribute username. I have some usernames in one list and I want to show details of these users only whose username is present in the list. How can I write a query to fetch details of those users only whose username is found in this list? And note that there is no lexicographical ordering so i can't use startAt() and endAt() functions as well.
code snippet:
=> myList contains usernames. This code doesn't yield accurate results.
Any help would be really appreciated! Thank you!
FirebaseRecyclerOptions<MainModel> options =
new FirebaseRecyclerOptions.Builder<MainModel>()
.setQuery(FirebaseDatabase.getInstance().getReference().child("users").orderByChild("username")
.startAt(myList.get(0)).endAt(myList.get(myList.size()-1)),MainModel.class).build();
As already mentioned in the comment, the Firebase-UI library doesn't help in your case, because it doesn't allow you to pass multiple queries to the FirebaseRecyclerOptions object. So you need to perform a separate query and use the combined result.
When you are calling .get() on a Firebase Realtime Database query object, you are getting back a Task object. So the key to solving this problem is to use whenAllSuccess(Collection> tasks). In your case, it should look like this:
DatabaseReference db = FirebaseDatabase.getInstance().getReference();
DatabaseReference usersRef = db.child("users");
Query query = usersRef.orderByChild("username");
List<Task<DataSnapshot>> tasks = new ArrayList<>();
for (String username : myList) {
tasks.add(query.equalTo(username).get());
}
Tasks.whenAllSuccess(tasks).addOnSuccessListener(new OnSuccessListener<List<Object>>() {
#Override
public void onSuccess(List<Object> list) {
//Do what you need to do with your list.
for (Object object : list) {
MainModel mm = ((DataSnapshot) object).getValue(MainModel.class);
if(mm != null) {
Log.d("TAG", mm.getUsername());
}
}
}
});
Assuming that you have in your MainModel class a getter called getUsername(), the result in your logcat will be all the usernames of all returned children.
I have a list of Keys in a Array List . I want to retrieve data only from those keys but at a same time.
DatabaseReference mDBRef;
List<String> keys = new ArrayList<>();
I tried this with loop but the result coming in Model class is repeated 2 times.
for (int i= 0;i<keys.size();i++)
{
String id = keys.get(i);
Log.d("Keys",id);
mDBRef = FirebaseDatabase.getInstance().getReference().child("Gyms").child(id);
mDBRef.addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(final DataSnapshot dataSnapshot) {
dataSnapshot.getKey();
gyms = dataSnapshot.getValue(Gyms.class);
if (gyms != null)
{
Log.d("Names",gyms.getName());
Toast.makeText(NearMeActivity.this, ""+ gyms.getName(), Toast.LENGTH_SHORT).show();
}
}
#Override
public void onCancelled(DatabaseError databaseError) {
}
});
}
The Firebase Realtime database doesn't support loading items based on an array of values. You'll have to load each item with a separate call.
While this may convolute your code, it is not nearly as slow as most developers think. This is because Firebase pipelines the requests. For a longer explanation of that, see my answer here: http://stackoverflow.com/questions/35931526/speed-up-fetching-posts-for-my-social-network-app-by-using-query-instead-of-obse/35932786#35932786
firebase real time database doesn't support multiple matches. You can see Firestore which is NoSQL and provide some flexibility.
See Firestore: https://firebase.google.com/docs/firestore/
You have to use custom search solution like Elastic Search.
I want to update the date on Firebase on a specific node.
DB Structure:
I am trying as
private void updateData() {
database = FirebaseDatabase.getInstance();
myref = database.getReference();
myref.child("myDb").child("awais#gmailcom").addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
dataSnapshot.getRef().child("leftSpace").setValue(newValue);
dialog.dismiss();
}
#Override
public void onCancelled(DatabaseError databaseError) {
Log.d("User", databaseError.getMessage());
}
});
}
I want to update the leftSpace key with the value of newValue, newValue is the type of string here. But it is not updating the value in Firebase.
If I give here
dataSnapshot.getRef().child("leftSpace").setValue(765);
it updates well. But I want to update in the format of string on the Firebase.
I saved the data on Firebase of all string types. (My pattern class contains all of the type strings)
Why it is not updating the newvalue of type string here?
Edit 1 Suggested by #Rjz Satvara
You method is adding a new node under myDB as
It is not updating the already one.
In Firebase To update specific value you can use it...
ref.child("myDb").child("awais#gmailcom").child("leftSpace").setValue("YourDateHere");
or you can move into child using "/" as follow :
ref.child("myDb/awais#gmailcom/leftSpace").setValue("YourDateHere");
you can assign new value in same child,like
Firebase firebase = new Firebase("your database link/myDb");
firebase.child("awais#gmail.com").child("leftSpace").setValue("newValue");
According to Firebase Official Documentation you can update the specific node of parent node in this way
Using setValue() in this way overwrites data at the specified location, including any child nodes. However, you can still update a child without rewriting the entire object. If you want to allow users to update their profiles you could update the username as follows:
FirebaseDatabase database = FirebaseDatabase.getInstance();
DatabaseReference mDatabaseRef = database.getReference();
mDatabaseRef.child("TABLE_NAME").child("orderStatus").setValue(2);
Note! TABLE_NAME mean your parent node whose child node you want to update.
I have the following data structure on firebase for the user MF0qeRA4p7djfjgXxqwFOck3m6p02. I want to get the value of item3 to populate a single field into the User interface on an Android App. I have been looking through samples on Stackoverflow, but all I have found are outdated and do not work with the current version of firebase. I'm new to firebase completely and this is my first app on android. I've got the oncreate user method to populate the users email address and add the 4 item fields, but retrieving the data I'm completely lost and I am not sure where to even begin.
-Users
---MF0qeRA4p7djfjgXxqwFOck3m6p02
------item1:"1"
------item2:"2"
------item3:"3"
------item4:"4"
According to what I can identify is, you are facing problem retrieving data from this reference. Here is the code:
final DatabaseReference databaseReference = FirebaseDatabase.getInstance().getReference("Users");
databaseReference.child("MF0qeRA4p7djfjgXxqwFOck3m6p02").addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
Map<String, Object> map=(Map<String, Object>)dataSnapshot.getValue();
String item3=(String)map.get("item3");
display(item3);
}
#Override
public void onCancelled(DatabaseError databaseError) {
}
});
Hope this helps.
You can create a custom model and inside you can insert elements. Something like this:
public class Item {
private List<Object> ojects;
}
There you can save instance of Item on database. In this case you have more controll. Other case is to use push() method, that will generate a new encoded key, something like this:
mDatabase.child("items").push().put(new Object());
I am trying to use the firebase push() function, as I want to add a list off data to an allready existing list. The setValue() function overwrites existing data.
This is what I used to do:
DatabaseReference childref = mDatabase.child("users").child(uih.getUserData().getUsername()).child("answered_questions");
childref.setValue(getAnsweredQuestions(questionViewList));
This worked, but every time I use this function the data is overwritten and this is not what I want. I tried using the Push function as described by the firebase documation: https://firebase.google.com/docs/database/android/save-data
I am not sure I am doing it right, but it is not working. And this is when I tried to implement the push() function:
DatabaseReference childref = mDatabase.child("users").child(uih.getUserData().getUsername()).child("answered_questions");
String key = childref.push().getKey();
Map<String, Object> childUpdates = new HashMap<>();
childUpdates.put( key, questionViewList);
mDatabase.updateChildren(childUpdates);
The exception I get is:
com.google.firebase.database.DatabaseException: Failed to parse node with class class scrambled.nl.generationr.QuestionView
And this is weird, since I did not receive this error while doing the setValue method. Can anyone explain what I am doing wrong and how I should push a list to firebase?
edit:
What I can do is:
DatabaseReference childref = mDatabase.child("users").child(uih.getUserData().getUsername()).child("answered_questions");
childref.push().setValue(getAnsweredQuestions(questionViewList));
In added the push() here. This works, but instead of just increasing my list, i add another layer in my list so I actually get an array of arrays instead of a longer list.
See here the result:
Saving a List of AnsweredQuestion objects:
This assumes you've followed the rules when designing your AnsweredQuestion.class so that the Java object can be used to store data in Firebase. If you need guidance for that check under the "Basic write operations" heading for saving data in the documentation.
//List of AnsweredQuestions
List<AnsweredQuestion> mAllAnswers;
....
//create the database reference that points to the correct parent node
//where answeres are stored for each user
DatabaseReference ref = mDatabase.child("users").child(uih.getUserData().getUsername()).child("answered_questions");
//Iterate over your List of AnsweredQuestion objects and use push() along with setValue()
//to store a single AnsweredQuestion object to a unique location in your database.
for(AnsweredQuestion answer : mAllAnswers){
ref.push().setValue(answer);
}
Retrieve all answered questions for a user:
//create List to store AnsweredQuestion object
List<AnsweredQuestion> mAllAnswers = new ArrayList<AnsweredQuestion>();
...
ref.addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
//iterate over datasnapshot to get/store each AnsweredQuestion object
if(datSnapshot.exists()){
for(DataSnapshot snapshot : dataSnapshot.getChildren()){
AnsweredQuestion answer = snapshot.getValue(AnsweredQuestion.class);
mAllAnswers.add(answer);
}
}
}
#Override
public void onCancelled(DatabaseError databaseError) {
//handle error
}
});
There are multiple ways to retrieve the answers for each user, using .addListenerForSingleValueEvent() is just one way. You can also use a FirebaseListAdapter or FirebaseRecyclerAdapter if you wanted to display the answers in a ListView or RecyclerView.