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.
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 am working on an application similar to tinder but to help users to find people to play a specific sport with.
I currently have the code searching the database for the gender of the users (that the user can be matched with). However each user in the database has a node that contains all of the sports the user can pick. If the user prefers a sport the value is saved as 'true' and if not, the value is saved as 'false'. The appropriate users are then shown on the app.
A screenshot of the database is shown below:
This is the code I have so far:
public void checkUserSex(){
final FirebaseUser user = FirebaseAuth.getInstance().getCurrentUser();
DatabaseReference userDb = usersDb.child(user.getUid());
userDb.addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
if (dataSnapshot.exists()){
if (dataSnapshot.child("sex"). getValue() != null){
userSex = dataSnapshot.child("sex").getValue().toString();
switch (userSex){
case "Male":
oppositeUserSex = "Female";
break;
case "Female":
oppositeUserSex = "Male";
break;
}
getOppositeSexUsers();
}
}
}
#Override
public void onCancelled(DatabaseError databaseError) {
}
});
}
public void getOppositeSexUsers(){
usersDb.addChildEventListener(new ChildEventListener() {
#Override
public void onChildAdded(DataSnapshot dataSnapshot, String s) {
if (dataSnapshot.child("sex").getValue() != null) {
if (dataSnapshot.exists() && !dataSnapshot.child("connections").child("pass").hasChild(currentUId) && !dataSnapshot.child("connections").child("play").hasChild(currentUId) && dataSnapshot.child("sex").getValue().toString().equals(oppositeUserSex)) {
String profileImageUrl = "default";
if (!dataSnapshot.child("profileImageUrl").getValue().equals("default")) {
profileImageUrl = dataSnapshot.child("profileImageUrl").getValue().toString();
}
cards item = new cards(dataSnapshot.getKey(), dataSnapshot.child("name").getValue().toString(), profileImageUrl);
rowItems.add(item);
arrayAdapter.notifyDataSetChanged();
}
}
}
#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) {
}
});
}
How do I change it from matching gender to matching the selected sport?
Unfortunately, Firebase Realtime database does not allow a query based on multiple properties. To achieve what you want, you don't need to restructure your database entirely, you just need to change it a little bit. To solve this problem, you need to add a new node for each sport. Every time you add a new user which plays golf, add it also in its corresponding sport node. Your new node should look like this:
Firebase-root
|
--- golfPlayers
|
--- userId1 : true
|
--- userId2 : true
With this structure you can query your database to get only the users who are playing golf. This can be done by attaching a listener on golf node and iterate on the DataSnapshot object.
This practice is called denormalization and is a common practice when it comes to Firebase. For a better understanding, I recommend you see this video, Denormalization is normal with the Firebase Database.
Note, what you are trying to do and cannot be solved using Firebase Realtime database, can be solved using Cloud Firestore. Give it a try.
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;
}
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) {
}
});
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!");
}
});