How to retrieve a specific group of children from firebase realtime database - java

I created a social media app. Suppose I have one array list which contains all the uids of users I follow on my app, stored in child of (following), I also created a child of (posts) which has all posts, posted by all users. The problem is I don't want to use snapshot.getChildern() to retrieve posts which equals following because it downloads whole data under 'posts' child. Is there any way I can retrieve only those posts which matches with 'following' array list?(for Posts Database looks like this)-->
Clicks / UID {title : "sky" imageurl : "https...." publisher : "UID" timestamp : "1666456456.."}
and second one --> follow / UID / following / other user's uid : true
functions I use :
private void checkFollowing(){
followingList = new ArrayList<>();
DatabaseReference reference = Util.getDatabase().getReference("follow")
.child(Objects.requireNonNull(FirebaseAuth.getInstance().getCurrentUser()).getUid())
.child("following");
reference.addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(#NonNull DataSnapshot snapshot) {
followingList.clear();
for (DataSnapshot dataSnapshot : snapshot.getChildren()){
followingList.add(dataSnapshot.getKey());
}
readClicksPosts();
}
#Override
public void onCancelled(#NonNull DatabaseError error) {
}
});
}
private void readClicksPosts() {
DatabaseReference reference = Util.getDatabase().getReference("Clicks");
reference.orderByChild("timestamp").addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(#NonNull DataSnapshot snapshot) {
clicks_models_List.clear();
for (DataSnapshot datasnapshot : snapshot.getChildren()) {
Clicks_Model clicks_model = datasnapshot.getValue(Clicks_Model.class);
for (String id : followingList) {
assert clicks_model != null;
if (clicks_model.getPublisher().equals(id)) {
clicks_models_List.add(clicks_model);
}
}
}
Collections.reverse(clicks_models_List);
clicksAdapter.notifyDataSetChanged();
progressBar.setVisibility(View.GONE);
DatabaseReference.goOffline();
}
#Override
public void onCancelled(#NonNull DatabaseError error) {
}
});
}
I just want to show posts from people I follow, what should I do if I don't to use .getChildren() method. Please keep in mind the posts are going to update frequently.

Related

Can't read Firebase database, data returns null

I'm trying to get a specific user from the database but I can't, also I would like to check if there is already a node with the same name (uid). I don't want to get the whole list of users and then retrieve one from it because I don't think that's good for perfomance, my idea is to directly get the user from the database
I tried many ways but everything I try returns null, I can't get the values from dataSnapshot. I use an interface to make sure I execute the code after the reading is done
currentUser = FirebaseAuth.getInstance().getCurrentUser();
mUserReference = FirebaseDatabase.getInstance().getReference("users/"+currentUser.getUid());
mUserReference.addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(#NonNull DataSnapshot dataSnapshot) {
user = dataSnapshot.getValue(User.class);
dataStatus.dataIsLoaded(user);
}
#Override
public void onCancelled(#NonNull DatabaseError databaseError) {
}
});
}
Here the interface
public interface DataStatus{
void dataIsLoaded(User user);
}
And when I try to get the user it shows NullPointerException
UserUtils userUtils = new UserUtils();
userUtils.getUser(new UserUtils.DataStatus() {
#Override
public void dataIsLoaded(User user) {
mUser = user;
}
});
User creation, when I call this method I do uid = currentUser.getUid()
public void createNewUser(String uid, String name, String email, DataStatus dataStatus) {
mUserReference = mDatabase.child("users").child(uid);
mUserReference.addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(#NonNull DataSnapshot dataSnapshot) {
user = new User(name, email);
mDatabase.child("users").child(uid).setValue(user);
dataStatus.dataIsLoaded(user);
}
#Override
public void onCancelled(#NonNull DatabaseError databaseError) {
}
});
}
Here the rules
{
"rules": {
"users": {
"$uid": {
".read": "$uid === auth.uid",
".write": "$uid === auth.uid"
}
}
}
}
Here's how my data base is organized
SOLVED
The problem was a piece of code, which I didn't include here, that was trying to get access to the user outside the dataIsLoaded interface method, and as onDataChange is asynchronous the user hadn't been downloaded yet so it was conflicting with the code.
Welcome to SO community
Please use .child() method instead of cascading nodes with "/" as below
Also check if the value of the datasnapshot is not null using .exists() method
currentUser = FirebaseAuth.getInstance().getCurrentUser();
mUserReference = FirebaseDatabase.getInstance().getReference.child("users").child(currentUser.getUid());
mUserReference.addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(#NonNull DataSnapshot dataSnapshot) {
if(dataSnapshot.exists()) {
user = dataSnapshot.getValue(User.class);
dataStatus.dataIsLoaded(user);
}
}
// rest of code
Feel free if you need further support
UPDATE
Please replace your listener with below
currentUser = FirebaseAuth.getInstance().getCurrentUser();
mUserReference = FirebaseDatabase.getInstance().getReference.child("users").child(currentUser.getUid());
Query query = FirebaseDatabase.getInstance().getReference.child("users").orderByKey()
.equalTo(currentUser.getUid());
query.addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(#NonNull DataSnapshot dataSnapshot) {
for (DataSnapshot singleSnapshot : dataSnapshot.getChildren()) { // dataSnapshot contains set of returned result of the query
User user = singleSnapshot.getValue(User.class);
if (user != null) {
dataStatus.dataIsLoaded(user);
}
}
}
}
Welcome to SO community.
When did you call your function to read the user content?
it might be an asynchronous problem.
To make sure, make a function where the user is the parameter and call it once you read something from the DB (onDataChange).
Plus, did you try to print out what is inside " user " when you read it? if it gives you null, the problem is within the path for sure. If so, use .child("users").child(currentUser.getUid())

How to fetch all parent nodes with similar child nodes on Firebase database Android?

Screen shot of my Database
I would like to query my database by returning the names of all parents nodes which have certain similar child nodes.
I am already fetching the name of one parent with respect to it's child, but I want to fetch the name of all parents with similar child value.
Suppose, I want to get the names of the Hotels which have the similar child node value of 2000 and have a Rating of 3-star.
final List<String> hotelNames = new ArrayList<String>();
final Query userQuery = FirebaseDatabase.getInstance().getReference().child("F-5").orderByChild("HotelTypeTwo");
userQuery.equalTo(varX).addListenerForSingleValueEvent(
new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
for (DataSnapshot foodSnapshot: dataSnapshot.getChildren()) {
hotelNames.add(foodSnapshot.getKey());
for (int i=0;i < hotelNames.size();i++)
{
Log.i("Value of element "+i,hotelNames.get(i));
}
}
}
#Override
public void onCancelled(DatabaseError databaseError) {
}
}
);
So to get all hotels where HotelTypeTwo is equal to Desi and get according to your screenshot as results: Islamabad Hotel and Islamabad Mariott Hotel, please use the follwing lines of code:
DatabaseReference rootRef = FirebaseDatabase.getInstance().getReference();
DatabaseReference fRef = rootRef.child("F-5");
Query query = fRef.orderByChild("HotelTypeTwo").equalTo("Desi");
ValueEventListener valueEventListener = new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
for(DataSnapshot ds : dataSnapshot.getChildren()) {
String hotelName = ds.getKey();
Log.d(TAG, hotelName);
}
}
#Override
public void onCancelled(#NonNull DatabaseError databaseError) {
Log.d(TAG, databaseError.getMessage()); //Don't ignore errors!
}
};
query.addListenerForSingleValueEvent(valueEventListener);
The result in your logcat will be:
Islamabad Hotel
Islamabad Mariott Hotel
Unfortunately, querying on multiple properties is not permitted in Firebase. A workaround can be my answer from the following post:
How to sort Firebase records by two fields (Android)
However, in Firestore compound queries are permitted:
citiesRef.whereEqualTo("state", "CO").whereEqualTo("name", "Denver");

Android-Expected a List while deserializing, but got a class java.util.HashMap

I want to get all list of restaurants from Firebase in android.
Here is my code:
boolean delivery;
String openTime, closeTime, restaurantName;
long likes;
List<String> utilities;
List<RestaurantModel> listRes;
DatabaseReference dataResReference;
public RestaurantModel(){
dataResReference = FirebaseDatabase.getInstance().getReference().child("restaurants");
}
public List<RestaurantModel> getallRestaurant(){
listRes = new ArrayList<>();
ValueEventListener valueEventListener = new ValueEventListener() {
#Override
public void onDataChange(#NonNull DataSnapshot dataSnapshot) {
for (DataSnapshot dataValues : dataSnapshot.getChildren()){
RestaurantModel restaurantModel = dataValues.getValue(RestaurantModel.class);
listRes.add(restaurantModel);
}
}
#Override
public void onCancelled(#NonNull DatabaseError databaseError) {
}
};
dataResReference.addListenerForSingleValueEvent(valueEventListener);
return listRes;
}
But i get an exception
Expected a List while deserializing, but got a class java.util.HashMap
at RestaurantModel restaurantModel = dataValues.getValue(RestaurantModel.class);
Update:
Base on Alex Mamo answser, i changed my code is:
public void getAllRestaurant(final WhereInterface whereInterface){
ValueEventListener valueEventListener = new ValueEventListener() {
#Override
public void onDataChange(#NonNull DataSnapshot dataSnapshot) {
//go to node restaurant because datasnapshot is parent node
DataSnapshot dataSnapshotRes = dataSnapshot.child("restaurants");
//
for (DataSnapshot valueRes : dataSnapshotRes.getChildren()){
RestaurantModel restaurantModel = valueRes.getValue(RestaurantModel.class);
whereInterface.getAllRestaurantModel(restaurantModel);
}
}
#Override
public void onCancelled(#NonNull DatabaseError databaseError) {
}
};
databaseReferenceRoot.addListenerForSingleValueEvent(valueEventListener);
}
But i still got this exception (:.
Anyone can know why?
And are there solution?
Thank you so much!
There are two problems in your code. The first one would be this error:
Expected a List while deserializing, but got a class java.util.HashMap
Which can be solved using the following lines of code:
ValueEventListener valueEventListener = new ValueEventListener() {
#Override
public void onDataChange(#NonNull DataSnapshot dataSnapshot) {
List<RestaurantModel> listRes = new ArrayList<>();
for (DataSnapshot dataValues : dataSnapshot.getChildren()){
RestaurantModel restaurantModel = dataValues.getValue(RestaurantModel.class);
listRes.add(restaurantModel);
}
//Do what you need to do with listRes
}
#Override
public void onCancelled(#NonNull DatabaseError databaseError) {
throw databaseError.toException(); //Don't ignore errors
}
};
dataResReference.addListenerForSingleValueEvent(valueEventListener);
And the second problem is that you cannot return something now that hasn't been loaded yet. Firebase APIs are asynchronous, meaning that onDataChange() method returns immediately after it's invoked, and the callback from the Task it returns will be called sometime later. There are no guarantees about how long it will take. So it may take from a few hundred milliseconds to a few seconds before that data is available. Because that method returns immediately, your listRes list you're trying to return, will be empty due to the asynchronous behavior of this method.
Basically, you're trying to return a value synchronously from an API that's asynchronous. That's not a good idea. You should handle the APIs asynchronously as intended.
A quick solution to this problem would be to use the listRes list only inside the onDataChange() method (as in the above lines of code), otherwise I recommend you see the last part of my answer from this post in which I have explained how it can be done using a custom callback. You can also take a look at this video for a better understanding.
Edit:
public void getAllRestaurant(){
ValueEventListener valueEventListener = new ValueEventListener() {
#Override
public void onDataChange(#NonNull DataSnapshot dataSnapshot) {
List<RestaurantModel> listRestaurant = new ArrayList<>();
for (DataSnapshot valueRes : dataSnapshot.getChildren()){
RestaurantModel restaurantModel = valueRes.getValue(RestaurantModel.class);
Log.d("Test", restaurantModel.getRestaurantName());
listRestaurant.add(restaurantModel);
}
//Do what you need to do with listRes
}
#Override
public void onCancelled(#NonNull DatabaseError databaseError) {
throw databaseError.toException();
}
};
DatabaseReference rootRef = FirebaseDatabase.getInstance().getReference();
rootRef.child("restaurants").addListenerForSingleValueEvent(valueEventListener);
}
I know this can be too late, but I wish my answer could help somebody. I know 2 ways to solve this problem. Once I have time I'll update my answer .
#Solution 1
First of all I copy & paste your code. It is not clear for me how did you add data to firebase, but I feel you added data directly to firebase.
so this is My MainActivity code :
public class MainActivity extends AppCompatActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
RestaurantModel rm;
List<String> utils;
//restaurant 1
rm = new RestaurantModel();
rm.closeTime = "12:00am";
rm.openTime = "12:00pm";
rm.delivery = true;
rm.likes = 300;
rm.restaurantName = "KFC";
utils = new ArrayList<>();
utils.add("free wifi");
utils.add("free Parking");
utils.add("free water");
rm.utilities = utils;
//write first restaurant to Firebase
rm.addRestaurantToFirebase(rm);
//restaurant 2
rm = new RestaurantModel();
rm.closeTime = "5:00am";
rm.openTime = "12:00pm";
rm.delivery = false;
rm.likes = 500;
rm.restaurantName = "SubWay";
utils = new ArrayList<>();
utils.add("free wifi");
utils.add("free Parking");
rm.utilities = utils;
//write Second restaurant to Firebase
rm.addRestaurantToFirebase(rm);
rm.getallRestaurant();
}
}
and this is RestaurantModel :
public class RestaurantModel {
public boolean delivery;
public String openTime;
public String closeTime;
public String restaurantName;
public long likes;
public List<String> utilities;
public List<RestaurantModel> listRes;
DatabaseReference dataResReference;
public RestaurantModel() {
dataResReference = FirebaseDatabase.getInstance().getReference().child("restaurants");
}
public List<RestaurantModel> getallRestaurant() {
listRes = new ArrayList<>();
ValueEventListener valueEventListener = new ValueEventListener() {
#Override
public void onDataChange(#NonNull DataSnapshot dataSnapshot) {
for (DataSnapshot dataValues : dataSnapshot.getChildren()) {
RestaurantModel restaurantModel = dataValues.getValue(RestaurantModel.class);
listRes.add(restaurantModel);
Log.d("restaurantModel", restaurantModel.restaurantName);
}
}
#Override
public void onCancelled(#NonNull DatabaseError databaseError) {
}
};
dataResReference.addListenerForSingleValueEvent(valueEventListener);
return listRes;
}
public void addRestaurantToFirebase(RestaurantModel rm) {
dataResReference.child(restaurantName).setValue(rm);
}
}
here the result in firebase
I'm not sure if this is the case, but may help others: Do not store null's on List's
Firebase stores List's as Map's with index as keys, when deserialized back it detects the correlative keys and it treats it as a 'List'
Null values are not stored, nulls delete the field
So a List with a null value won't have all indexes and, when parsed, Firebase thinks that it's a Map.
Store some kind of "empty" value instead of null and it'll work.
Side note: if you have a map with correlative indexes it'll happen a similar error, just add some not numeric prefix on keys.
Kotlin Example
This is a example to get all your nested data out of firebase real time database.
There are many tokens in this example and every token contains list of notes
val noteListener = object : ValueEventListener {
override fun onDataChange(dataSnapshot: DataSnapshot) {
// For each loop will run until all notes are fetched
for (snapshot in dataSnapshot.children){
val note = snapshot.getValue(Note::class.java)
Log.e("Note",note?.title)
}
}
override fun onCancelled(databaseError: DatabaseError) {
// On failed, log a message
Log.e("Debug", "loadPost:onCancelled", databaseError.toException())
}
}
//You can use the addListenerForSingleValueEvent() method to simplify this scenario: it triggers once and then does not trigger again.
//ref.child(token) is the path for reach notes for each unique user , in your case it can be ref only
ref.child(token).addListenerForSingleValueEvent(noteListener)
Pre-requisit: We are setting the Snapshot response to our model class objects.
In our Model class. there must be one or more objects which we are setting as list.
Now in response, the ArrayList inside the Snapshot is coming to us as hashmap but we need list.
So the Solution i find is:
set the first layer of data to model objects as it is using limited parameter constructor.
Then the ArrayList object - Get it as a separate object of that specific class and then add it into the model class by getter.
Follow the below code sample.
Lets suppose you have Object Model class. And InnterList as an array list. My solution is to take innerList as a seperate object and then add it to model class using constructors.
If this list is not comming in response. You can handle them using limited constructor parameters.
Object object;
FirebaseDatabase.getInstance().getReference().getRoot().child("yourKey").addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(#NonNull DataSnapshot snapshot) {
for (DataSnapshot temp : snapshot.getChildren()) {
if (temp.hasChild("InnerList")) {
List<InnerList> innerList = new ArrayList<InnerList>();
DataSnapshot yourInnerArrayListSnapShot = temp.child("InnerList");
for (DataSnapshot innerTemp : yourInnerArrayListSnapShot.getChildren()) {
InnerList yourInnerArrayListObject = innerTemp.getValue(InnerList.class);
innerList.add(yourInnerArrayListObject);
}
object = new Object(a, b, innerList);
} else {
object = new Object(a, b);
}
}
}
#Override
public void onCancelled(#NonNull DatabaseError error) {
Log.d(TAG, "onCancelled error is : " + error.getMessage());
pd.dismiss();
}
});
Enjoy !!

Retrieve data from Firebase for multiple child nodes

I am working on app where I want to show the data only of the users whose contact numbers are saved in the phone. So, I retrieved a list of contact numbers contactList. Now, I want to get all the contactList user's post from Firebase. Is there any way I can ask the Firebase for only those nodes in my contactList. One Way is to retrieve all users and then get relevant users from that (like I did below). Is there any better way to do so ?
Users: {
7828272892 : {
name: xyz,
gender: male,
phoneNo: 7828272892
Posts: {
SomeKey1: {
content: "This is post 1", Var2: "kkk"} }},
7924272894 : {name: abc, gender: male, phoneNo: 7924272894}
}
Code:
databaseReference.child("Users").addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot snapshot) {
List<Post> allItems = new ArrayList<Post>();
for (DataSnapshot postSnapshot: snapshot.getChildren()) {
if (contactList.contains(postSnapshot.child("phoneNo").getValue())) {
// retrieve posts
}
}
}
#Override
public void onCancelled(#NonNull DatabaseError databaseError) {
}
});
Is there any way I can ask the firebase for only those nodes in my contactList?
Yes, the way in which you already do this.
Is there any better way to do so?
The way in which you do this is a common practice used in Firebase. You query the database and check is the data already exists in the list, in your case in the contactList. If the list contains that phoneNo then you can retrieve the posts.
Try this code to read multiple nodes data..
mFirebaseInstance = FirebaseDatabase.getInstance();
mDatabase = mFirebaseInstance.getReference("usersDb/UserTable");
mDatabase.addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
mUserList.clear();
for (DataSnapshot dataSnapshot1 : dataSnapshot.getChildren()) {
User user = dataSnapshot1.getValue(User.class);
mUserList.add(user); // add all data into list.
}
}
#Override
public void onCancelled(DatabaseError databaseError) {
}
});

Android - Get key if data exists (Firebase)

In one of my activities I insert some data to the Firebase database. In another activity I want to check if the child of the inserted data exists. If the key exists I want to get the root unique key.
Problem:
I have this database structure:
I want to check if the username child exists. If it exists I want to get the root key. Both the root unique key and the username node is marked with red.
What I have done
//variable username is the actual username of the current user
userRef.child(sharedPreferences.getString("school", null)).child("routes").orderByChild("username").equalTo(username).addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
for (DataSnapshot ds : dataSnapshot.getChildren()) {
String key=ds.getKey();
System.out.println("key" + key);
}
}
But I don't now how to get inside the drivers node, and how to get the root unique key. Any ideas?
Yes you can, using exists() method.
userRef.child("routes").child(routeId).child("drivers").child(userName).addListenerForSingleValueEvent(new
ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
if(dataSnapshot.exists()){
//do something
}
}
If you want to verify the existents of username accross all routes, assuming that routes node is a direct child of your Firebase database, please use the following code:
DatabaseReference rootRef = FirebaseDatabase.getInstance().getReference();
DatabaseReference routesRef = rootRef.child("routes");
ValueEventListener eventListener = new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
for(DataSnapshot ds : dataSnapshot.getChildren()) {
if(ds.child("drivers").child(userName).exists()) {
//do what you want
}
}
}
#Override
public void onCancelled(DatabaseError databaseError) {}
};
routesRef.addListenerForSingleValueEvent(eventListener);
In which userName is the name of the user that you want to be checked for existent.
You're almost there. You can pass in a path to orderByChild() to filter on the property at that path. So:
userRef.child(sharedPreferences.getString("school", null))
.child("routes").orderByChild("drivers/username").equalTo(username).addListenerForSingleValueEvent(new ValueEventListener() {
Now your onDataChange will get called with a snapshot of all nodes that have the required user name. So looping over them and printing is the same as you had:
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
for (DataSnapshot ds : dataSnapshot.getChildren()) {
String key=ds.getKey();
System.out.println("key" + key);
}
}
if(ds.hasChild("drivers")){
String key=ds.getKey();
}
you can do this:
userRef.child("routes").orderByChild("username").equalTo("jju").addListenerForSingleValueEvent(...){ //code here }
edit:
userRef.child("routes").addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
for (DataSnapshot ds : dataSnapshot.getChildren()) {
if(ds.hasChild("driver"){
for(DataSnapshot data : ds.getChildren()){
if(data.child("username").exists()){
String key=ds.getKey();
System.out.println("key" + key);
}
}
}
}
}

Categories

Resources