Is there a way to perform a simple query on a Firebase database that gets and returns one object matching the query parameter (in Java)? I'm very new to using Firebase and as far as I can understand the documentation, orderBy() is an asynchronous method that lasts indefinitely, which is why I'm having trouble figuring out how to perform operations on data after the query. Is there some callback notation that allows me to quickly retrieve and return a value and end the query, or am I missing something in the Firebase documentation?
For example, in this method, I want to just figure out if the database contains a specified user and return true if the query matches the user.
public void containsUser(String user) {
DatabaseReference ref = getDatabaseRef("users");
ref.orderByKey().equalTo("user1").addChildEventListener(new ChildEventListener() {
#Override
public void onChildAdded(DataSnapshot dataSnapshot, String s) {
System.out.println(dataSnapshot.getKey());
}
#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) {
System.out.println(databaseError.getMessage());
}
});
}
Is there an easy way to do this?
To get a single value use addListenerForSingleValueEvent
DatabaseReference ref = getDatabaseRef("users");
ref.child("user1").addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
String value = (String) dataSnapshot.getValue();
// do your stuff here with value
}
#Override
public void onCancelled(FirebaseError firebaseError) {
}
});
Related
I'm trying to create an observable that returns a list from a firebase query. the problem is when I call onNext to emit the Item then onComplete it stops emitting items which is after the first item, and not calling onComplete at all emits nothing. Is there a correct way to do what I'm trying to achieve? I'm very new to RxJava still, so please excuse my ignorance. thank you in advanced for any help :)
public Observable<Message> getMessageObservable(String uid) {
currentUser = auth.getCurrentUser();
DatabaseReference db_messages = db_root.child("Messages").child(currentUser.getUid())
.child(uid);
Query messageQuery = db_messages.orderByKey().limitToLast(10);
return Observable.create(emitter -> {
messageQuery.addChildEventListener(new ChildEventListener() {
#Override
public void onChildAdded(#NonNull DataSnapshot dataSnapshot, #Nullable String s) {
String messageText = dataSnapshot.child("message").getValue().toString();
String messageId = dataSnapshot.child("MessageId").getValue().toString();
Boolean seen = dataSnapshot.child("seen").getValue(Boolean.class);
Long timestamp = dataSnapshot.child("timestamp").getValue(long.class);
String fromUser = dataSnapshot.child("from").getValue().toString();
String toUser = dataSnapshot.child("to").getValue().toString();
Message message = new Message(messageText, toUser, messageId, seen, timestamp, null, fromUser);
emitter.onNext(message);
emitter.onComplete();
}
#Override
public void onChildChanged(#NonNull DataSnapshot dataSnapshot, #Nullable String s) {
}
#Override
public void onChildRemoved(#NonNull DataSnapshot dataSnapshot) {
}
#Override
public void onChildMoved(#NonNull DataSnapshot dataSnapshot, #Nullable String s) {
}
#Override
public void onCancelled(#NonNull DatabaseError databaseError) {
}
});
});
}
#Override
public void getMessages(String userId) {
currentUser = auth.getCurrentUser();
Observable.just(userId)
.flatMap(this::getMessageObservable)
.toList()
.subscribe(messages -> {
chatResults.getMessagesResult(messages);
});
}
There are as always many ways to solve the problem. Please check if this one works for you:
Change getMessageObservable to simple method that gets reference, query and adds ChildEventListener listener (no observables created etc)
Create PublishSubject<String> myMessages = PublishSubject.create() pub subject, subscribe to it as you'd usually do with observables. In your subscription make sure to listen to onNext action (Action1)
In your ChilddEventListener impl, make sure to call myMessages.onNext(message) once new messages arrives
With the above setup, you'll now have messages coming to your onNext subscription. You could keep mutable list and append (or prepend) coming messages, thus notifying interested parties re updated list of messages.
I'm trying the add the retrieved values from Firebase database to an Arraylist and from there to a String array. My retrieval method works fine. I can have all the values printed out in a toast. But apparently it doesn't get added to the arraylist.
Here's my code for retrieval in onActivityCreated() of fragment class.
ArrayList<String> allBrands = new ArrayList<>();
brandRef=FirebaseDatabase.getInstance().getReferenceFromUrl("https://stockmanager-142503.firebaseio.com/Brands");
q=brandRef.orderByChild("brandName");
q.addChildEventListener(new ChildEventListener() {
#Override
public void onChildAdded(DataSnapshot dataSnapshot, String s) {
allBrands.add((dataSnapshot.getValue(Brand.class)).getBrandName());
Toast.makeText(getActivity(),(dataSnapshot.getValue(Brand.class)).getBrandName(), Toast.LENGTH_SHORT).show();
}
#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) {
}
});
And this is where I'm trying to use the arrayList in OnActivityResult() method of the Fragment class but the iterator loop is not executed I believe. The toast is not seen. I'm getting a null pointer exception when I try to work with the array. I assume the values do not get copied to the brands array.
count=allBrands.size();
String[] brands=new String[count];
Iterator<String> itemIterator = allBrands.iterator();
if(itemIterator.hasNext()){
//brands[i] = itemIterator.next();
Toast.makeText(getActivity(), itemIterator.next(), Toast.LENGTH_SHORT).show();
// i++;
}
for( i=0;i<count;i++){
if(brands[i].compareTo(Brand)==0){
f=1;break;
}
}
Here's my database in case that helps. But I can print out all the retrieved values in a Toast with no problem.
It's hard to be certain from the code you shared, by I suspect you may be bitten by the fact that all data is loaded from Firebase asynchronously. Alternatively you may simply not have permission to read the data. I'll give an answer for both.
Data is loaded asynchronously
It's easiest to understand this behavior when you add a few log statements to a minimal snippet of your code:
System.out.println("Before attaching listener");
q.addChildEventListener(new ChildEventListener() {
public void onChildAdded(DataSnapshot dataSnapshot, String s) {
System.out.println("In onChildAdded");
}
public void onChildChanged(DataSnapshot dataSnapshot, String s) { }
public void onChildRemoved(DataSnapshot dataSnapshot) { }
public void onChildMoved(DataSnapshot dataSnapshot, String s) { }
public void onCancelled(DatabaseError databaseError) { }
});
System.out.println("After attaching listener");
The output of this snippet will be:
Before attaching listener
After attaching listener
In onChildAdded (likely multiple times)
This is probably not the order you expected the output in. This is because Firebase (like most cloud APIs) loads the data from the database asynchronously: instead of waiting for the data to return, it continues to run the code in the main thread and then calls back into your ChildEventListener.onChildAdded when the data is available.
There is no way to wait for the data on Android. If you'd do so, your users would get the daunted "Application Not Responding" dialog and your app would be killed.
So the only way to deal with the asynchronous nature of this API is to put the code that needs to have the new data into the onChildAdded() callback (and likely into the other callbacks too at some point):
q.addChildEventListener(new ChildEventListener() {
public void onChildAdded(DataSnapshot dataSnapshot, String s) {
allBrands.add((dataSnapshot.getValue(Brand.class)).getBrandName());
System.out.println(allBrands.length);
}
You need permission to read the data
You need permission to read the data from a location. If you don't have permission, Firebase will immediately cancel the listener. You need to handle this condition in your code, otherwise you'll never know.
public void onCancelled(DatabaseError databaseError) {
throw databaseError.toException();
}
Try this (I'm writing this to future reference of myself .. too)
As you can see we implement a reresh before it's end. There is probably a nicer way to do it. However, it is not documented. Also all this event Listners should be add autmoatically and released automatically by firebase but they don't do it from some reason.
/**
* #param uid User's ID
* #param Callable send as null just to implement a call to assure the callback is updapted before it's finished
* #return returns ArrayList of all Games unique identification key enlisted in a User
*/
private final ArrayList<String> mGamesPlaying = new ArrayList<>();
public ArrayList<String> mGamesPlaying(final String uid, final Callable refresh) {
final Firebase ref = FirebaseRef;
Firebase usersRef = ref.child("users").child(uid).child("playing_games");
usersRef.addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot snapshot) {
mGamesPlaying.clear();
for (DataSnapshot child : snapshot.getChildren()) {
Log.d(TAG, "Test Game" + child.getKey());
mGamesPlaying.add(child.getKey());
}
try {
refresh.call();
} catch (Exception e) {
e.printStackTrace();
}
}
#Override
public void onCancelled(FirebaseError firebaseError) {
}
});
return mGamesPlaying;
}
There is a users subtree in my Firebase Database which keeps basic user info and following/followers for that user, this subtree is structured like this:
"users": {
"userId-1": {
"userName": "Namey McNameface",
"following": {
"followingId-1": true,
"followingId-2": true,
.
.
},
"followers": {
"followerId-1": true,
"followerId-2": true,
.
.
}
},
"userId-2": {},
"userId-3": {},
.
.
}
I want to send a notification to user's phone whenever someone starts to following him/her.
In my own server, I listen to the followers subtree. When a child added, user will see a notification.
The problem is that, at some point I will need to deploy the next version of my server and when I do that OnChildAdded event will fire for all children (followers) and all users will see wrong notifications about their followers starting to follow them.
I can store the sent-notification info in my database to solve this problem, but this will be a time-consuming job for me.
Another way is that I can skip the initial firing of OnChildAdded event, if that is possible in Firebase Database.
Thank you in advance.
Edit:
I am using Java and Spring for my server.
This is my FirebaseService. I am creating a UserListener and starting to listen all users.
#Service
public class FirebaseService {
public FirebaseService() {
UserListener userListener = new UserListener();
FirebaseDatabase.getInstance().getReference(Tag.USERS).addChildEventListener(userListener);
}
}
This is UserListener. It creates a FollowerListener to listen followers subtree for each user.
public class UserListener implements ChildEventListener {
#Override
public void onChildAdded(DataSnapshot dataSnapshot, String s) {
String userId = dataSnapshot.getKey();
FollowerListener followerListener = new FollowerListener(userId);
FirebaseDatabase.getInstance().getReference(Tag.USERS).child(userId).child(Tag.FOLLOWERS).addChildEventListener(followerListener);
}
#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) {
}
}
This is FollowerListener. When a user ID is added to followers, FollowerListener sends notification. It basically gets the followed user's FirebaseInstanceId and follower user's screen name. Then sends notification.
public class FollowerListener implements ChildEventListener {
private final String followedUserId;
public FollowerListener(String followedUserId){
this.followedUserId = followedUserId;
}
#Override
public void onChildAdded(DataSnapshot dataSnapshot, String s) {
String followerUserId = dataSnapshot.getKey();
FirebaseDatabase.getInstance().getReference(Tag.USERS).child(followedUserId)
.addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
User followedUser = dataSnapshot.getValue(User.class);
if(followedUser != null){
FirebaseDatabase.getInstance().getReference(Tag.USERS).child(followerUserId)
.addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
User followerUser = dataSnapshot.getValue(User.class);
if(followerUser != null){
// Send notification to followed user.
NotificationInfo notificationInfo = new NotificationInfo();
notificationInfo.setTitle("test title");
notificationInfo.setText(followerUser.getScreenName() + " seni takip etmeye başladı.");
notificationInfo.setClickAction(Tag.ACTION_GO_TO_PROFILE);
NotificationData data = new NotificationData();
data.setUserId(followerUserId);
NotificationRequestBean bean = new NotificationRequestBean();
bean.setTo(followedUser.getFirebaseInstanceId());
bean.setBody("test body");
bean.setTitle("test title");
bean.setPriority("normal");
bean.setDelayWhileIdle(false);
bean.setNotification(notificationInfo);
bean.setData(data);
AppUtils.sendNotification(bean);
}
}
#Override
public void onCancelled(DatabaseError databaseError) {
}
});
}
}
#Override
public void onCancelled(DatabaseError databaseError) {
}
});
}
#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) {
}
}
The problem is whenever I deploy the server, FollowerListener attaches to all users's followers subtree. Then OnChildAdded event is firing for each follower initially. It should not send notifications every time I deploy the server.
Thanks.
I've found a very simple solution for this problem. I just added a boolean field in the followers subtree called processedByServer. So it became like this:
"followers": {
"followerId-1": {
"processedByServer": true // already processed.
},
"followerId-2": {
"processedByServer": false // not processed yet, but will be.
},
.
.
}
This processedByServer is initially false, after I made my necessary processing in the server (send notification etc.) I change this value to true. The server only needs to process when this field is false. So after I redeploy/reboot my server, it won't send wrong notifications anymore.
But there is also downsides of this solution:
I added an extra field just for that, but I think it's ok.
When the server restarts, it has to check all of the processedByServer fields in the followers subtree, which may be a time consuming job for the server.
I created A method that could be able to get the data in my firebase using the userId when I debugging it, it couldn't be able to go inside am wondering whats wrong with my code. This is my code:
public static String GetUserRole(final String userId){
Role = null;
getUsersRef().child(userId).addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
String role = dataSnapshot.getValue().toString();
}
#Override
public void onCancelled(DatabaseError databaseError) {
}
});
return Role;
}
is there any problem with this?
When implementing the ValueEventListener, the overridden methods are called when an event happens. this means that it isn't called and executed when the GetUserRole method is executed, but it is called separately, when the event happens (data received from Firebase). If you print a log you will see that the onDataChange method or the onCancelled method will be called (after GetUserRole run).
Try adding a Log print like this:
getUsersRef().child(userId).addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
Log.d(TAG, "onDataChange!");
}
#Override
public void onCancelled(DatabaseError databaseError) {
Log.d(TAG, "onCancelled!");
}
});
So in the above data structure, I want to get value Aprilia Caponord 1200 (marked in fig.). I have Firebase reference to Aprilia (root node) and I can get all the key/value pair data.
Here's my code
#override
public void onDataChange(DataSnapshot dataSnapshot) {
for (DataSnapshot child : dataSnapshot.getChild()) {
Map<?, ?> value = (Map<?, ?>) child.getValue();
String bike_price = value.get("price").toString();
String image_url = value.get("image_url").toString();
}
}
So clearly I can get all the values of child nodes having key/value relations. But I can't specifically get the name of the parent node. So how can I get the value of parent node in String format?
If I print child.getValue().toString(), I get JSON value, but I am not interested in parsing JSON. Is there any specific way to get parent node value?
{Aprilia Caponord 1200={image_url=____________, price=18.35}.. }
You're probably looking for the getKey() method on DataSnapshot:
mFirebaseRef = new Firebase("https://yours.firebaseio.com");
mFirebaseRef.addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
for (DataSnapshot child: dataSnapshot.getChildren()) {
Log.i("MainActivity", child.getKey());
}
}
#Override
public void onCancelled(FirebaseError firebaseError) {
Log.e("MainActivity", "onCancelled", firebaseError.toException());
}
});
A few things to note here:
I changed getChild() in your code to getChildren(), since there is not method getChild() that I'm aware of.
You should consider using addChildEventListener, which gives you DataSnapshots on the lower-level nodes
Things will get considerably easier if you create "wrapper classes" for your cars. The Firebase SDK for Android will then automagically unwrap the JSON into instances of that class. See our AndroidChat repo for a good example of this.
I met this case and solved the way as below:
final ChildEventListener countsListener = new ChildEventListener() { //for querying how many card created by each user in a specific period
#Override
public void onChildAdded(#NonNull DataSnapshot dataSnapshot, #Nullable String s) {
/**
* $ROOT/Apirilia/Apirilia_id/CardId/IMG_URL
* $ROOT/Apirilia/Apirilia_id/CardId/PRICE
* ^[key] :[value]
*/
if (dataSnapshot == null) {
Logger.e("!!! dataSnapshot is null");
return;
}
String apirilia_id = dataSnapshot.getRef().getParent().getKey();
Logger.d("... Apirilia_id:" + apirilia_id);
}
#Override
public void onChildChanged(#NonNull DataSnapshot dataSnapshot, #Nullable String s) {
}
#Override
public void onChildRemoved(#NonNull DataSnapshot dataSnapshot) {
}
#Override
public void onChildMoved(#NonNull DataSnapshot dataSnapshot, #Nullable String s) {
}
#Override
public void onCancelled(#NonNull DatabaseError databaseError) {
}
};
I'm appreciated to be able to give one a hand.
for those who looking for key in firebaseUi then this code will work for u:-
firebaseadapterinstance.getRef(position).getKey();