Removing nodes from Firebase best practices - android

I made a firebase project where i have 2 android apps connected to the same Database, let's say the first app is an "user app" which i'm distributing to some friends and the second one is the "admin app"(an app that is only on my personal phone). The "user app" basically just displays some infos about events in my city, while i use the "admin app" to add/delete those events from the database
In my user app i have a pretty standard RecyclerView displaying some cards using FirebaseRecyclerAdapter. Clicking on a card leads to a new Activity which displays more info about the event, using an Intent i pass an Id as a String which loads the corrisponding node in the database and everything works just fine. The problem is that when i delete a node (from Firebase console or from amy "admin" app) sometimes the "card" in my recyclerview is still avaible and clicking on it leads to a NullPointerExeption because the object i'm looking for is obviously no longer in the database.
I'm using .keepSynced(true) on my DatabaseReference and i've set setPersistanceEnabled and i think this might be the problem.
my Adapter declaration #OnStart looks like this :
if(recyclerView.getAdapter == null){
eventAdapter = new FirebaseRecyclerAdapter<DynamicData,EventViewHolder>
(
DynamicData.class,
R.layout.event_card,
EventViewHolder.class,
eventRef.orderByChild("eName")
){
#Override
protected void populateViewHolder(EventViewHolder viewHolder, DynamicData model, int position) {
final String post_key = getRef(position).getKey();
viewHolder.setDate(model.getDate());
viewHolder.setId(post_key);
viewHolder.setName(model.geteName());
viewHolder.setTime(model.getDate());
viewHolder.mView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
Intent toEvent = new Intent(getActivity(), EditEvent.class);
toEvent.putExtra("EVENT_ID",post_key);
startActivity(toEvent);
}
});
}
};
}
recyclerView.setAdapter(eventAdapter);
if(rcState != null){
recyclerView.getLayoutManager().onRestoreInstanceState(rcState);
}
so nothing really strange is going on here.
My OnSavedInstanceState :
#Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putParcelable(BUNDLE_RECYCLER_LAYOUT, linearLayoutManager.onSaveInstanceState());
rcState = outState.getParcelable(BUNDLE_RECYCLER_LAYOUT);
}
The if(recyclerView.getAdapter() == null) clause that i use OnStart togheter with the code in OnSavedInstanceState is the only thing that makes my recycler view scroll to the right position. Without this code whenever i change activiy with the Intent (by clicking on a recyclerView Item) and then come back the recyclerview gets recreated and it starts once again from top
Important : right now i'm calling
#Override
public void onDestroy() {
super.onDestroy();
recyclerView.setAdapter(null);
eventAdapter.cleanup();
}
rather than onStop() since it's apparently the only way i can keep my recyclerView position when switching activity.
In my "admin app" when i want to delete some event from the db i use this code :
//Query fetching events older than 6 hours
Query returnsixHoursOldEvents =
eventRef.orderByChild("date").endAt(six_hours_cutoff);
returnsixHoursOldEvents.addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
for(DataSnapshot postSnapshot : dataSnapshot.getChildren()){
postSnapshot.getRef().removeValue();
}
}
now the problem is that since i'm deleting this events from my "admin app" i can't notify the adapter on my "user app". What happens is that sometimes (seems to happen randomly) if a friend of mine opens his app he can still see the events i just delete in his recyclerview, but if he clicks on it the app crashes since the Node in the database is no longer avaible
is keepSynced or setPersistanceEnabled the problem ? any work around ?

In a shared data model it is quite possible that one user deleted data that another user still sees, especially when one of them is not connected to Firebase for a while. Your code will have to handle that situation.
Methods like removeValue() are immune to whether the data exists or not: they just ensure that after they've execute, the data is gone.

Related

Load multiple SingleValueEvents at once with Android Firebase

I want to start a Fragment with information I have to get from the Firebase database when I click the button. For the sake of an example, say I want to open a user profile and I need to load all data from database > users > uid AND I need information from database > messages (e.g. the posts of the user).
Now my approach would be this:
(The following code is a part of the #Override public void onClick(View v) { } method of the button that shall start the Fragment)
dbRef.addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot snapshot) {
User user = snapshot.getValue(User.class);
// start the fragment here ...
}
});
But since I need information from database > messages as well, I'm not sure how to do this in a good way. The following would be possible:
(The following code is a part of the #Override public void onClick(View v) { } method of the button that shall start the Fragment)
dbRef.addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot snapshot) {
User user = snapshot.getValue(User.class);
// just add another listener on the other reference
dbRef_2.addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot snapshot) {
MessagesInfo message = snapshot.getValue(MessagesInfo.class);
// start the fragment here ...
}
});
}
});
But this seems not to be the best way to do it since we just got two SingleValueEvent listeners nested. And if we'd need more information, like 10 different database "locations", we'd need to nest 10 of these listeners.
So what is a good way?
Unfortunately, there is no method to fetch multiple locations at once, you could do this with a custom script if you are expecting to re-use it multiple times but it would always result in a loop that fetches each item.
It's also important to note that the order in which the queries were sent is also the order in which the server processes them.

Firebase event listener that calls only when the data updates android

I want to send a notification whenever the data in firebase changes.
But when I use addValueEvenListener() method it returns more than once. After that I tried using addListenerForSingleValueEvent() method but it now returns 2 times, When I start the app and when the data changes. Is there a way for it to return only one time which is when the data changes and not when the app starts?
Here is my code for now:
databaseReference.addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(#NonNull DataSnapshot snapshot) {
sendNotification("notification","App name",getIntent().getStringExtra("storeid"));
}
#Override
public void onCancelled(#NonNull DatabaseError error) {
}
});
EDIT:
Just so every one understand my question.
When I start the app, a notification is sent because of the code above. And I don't want this to happen, Instead I need to only send the notification when the data changes.
Let's say you have one boolean variable
declared globally in the class where you register the listener
boolean isCalled=false;
Reset it just before registering the listener.
isCalled=false;
databaseReference.addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(#NonNull DataSnapshot snapshot) {
if(isCalled){
//it is already called once.
sendNotification("notification","App name",getIntent().getStringExtra("storeid"));
}else{
//called first time
isCalled=true;
}
}
#Override
public void onCancelled(#NonNull DatabaseError error) {
}
});
According to the Docs, the ValueEventListener returns a Value not just when the data is changed but also when the method is first executed.
As far as I can tell, there are two possible solutions to your problem:
You use a background service in order to keep the method running in the background so that it does not return a value for each time you open the app. This would also mean, that you would get notifications when the app is not even open. I don't know if that is in your interest.
Store the value and check it manually each time. You can save the returned value to the storage and check if the new value is different from the prior value each time the listener executes.
I hope I could help, happy coding
Data stored in the Firebase Realtime Database is retrieved by attaching it to an asynchronous listener data source. The listener is triggered again once for its initial state and each time its data changes. In your case, no clear solution is presented in the documentation.

Firebase iterations are slow

I'm using firebase with android to create a simple chat app. When the user chooses another user to chat with I want to check whether they've chatted together or not.
In onCreate() method I'm retrieving all the rooms that the current user used before, and I'm putting them in an arraylist called MyChatRooms<>.
Then I want to check each room to see the users of the room.
The problem is that the loop I'm using to iterate through rooms name is finishing before I'm able to retrieve any data from the database.
I know there's similar questions to mine, but none of the answers worked for me.
Here's the related code:
if (!MYChatRooms.isEmpty()) {
for (j = 0; j < MYChatRooms.size(); j++) {
roomref.child(MYChatRooms.get(j)).child("First User").addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot1) {
if (!dataSnapshot1.getValue().toString().equals(Username) && dataSnapshot1.getValue().toString().equals(NUsername)) {
Users += dataSnapshot1.getValue().toString() + ",,, ";
} else if (dataSnapshot1.getValue().toString().equals(Username)) {
roomref.child(MYChatRooms.get(j)).child("Second User").addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot2) {
if (dataSnapshot2.getValue().toString().equals(NUsername)) {
Users += dataSnapshot2.getValue().toString() + ",,, ";
}
}
#Override
public void onCancelled(DatabaseError databaseError) {
}
});
}
}
#Override
public void onCancelled(DatabaseError databaseError) {
}
});
}
}
I would suggest that you change the structure of your data. Imagine if a user has 100 chats that means your have to query 200 times to Firebase that of course does not look feasible.
What i would suggest is that your add a recentChat list in every user and whenever a user starts a new chat with someone you add the id of the second user to that list. That way you can track easily with whom the current user has interacted with.
It structure in firebase can look something like this:
User
recentChats
id of the other user
Try to change you database hierarchy or use firestore instead of real time database
Please check the following topic: https://firebase.google.com/docs/database/usage/optimize?
In my case, I had added and index column and limited the query in Firebase Rules.

Welcome User screen firebase integration - unexpected results

I have a key 'newUser' within my firebase database, which can be set to either 'true' or 'false', as shown here:
firebase database structure click here for screenshot
I have referenced to the database within a LoginActivity class, upon clicking a login button the userLogin method is called, within this method I perform the following:
mDatabase.orderByChild("email").equalTo(email)
.addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
for(DataSnapshot singleSnapshot: dataSnapshot.getChildren()){
String newUser = singleSnapshot.getValue(User.class).newUser;
if(newUser.equals("true")){
Log.d("Yes!", "this is a new user");
startnewActivity(singleSnapshot);
}else {
Log.d("No!", "this is an old user");
startnewActivity(singleSnapshot);
}
}
}
#Override
public void onCancelled(DatabaseError databaseError) {
}
});
Note that equalTo(email) ... email is a local variable which grabs the text within the textfield where the user inputs the email.
the startnewActivity method is defined below, to initialise the activities dependent on whether the user is new or not:
private void startnewActivity(DataSnapshot singleSnapshot) {
String s = singleSnapshot.getValue(User.class).newUser;
if (s.equals("true")) {
Intent i = new Intent(LoginActivity.this, AudiometryNewUser.class);
singleSnapshot.getRef().child("newUser").setValue("false");
Log.d("iterated!?", "");
startActivity(i);
return;
}
else if(s.equals("false")){
Intent i = new Intent(LoginActivity.this, MainDashboard.class);
Log.d("iterated", "");
startActivity(i);
}
}
my intention is when the user is a newUser = true, then they will be taken to the welcome screen AudiometryNewUser, and for the newUser value to adopt "false".
The result however is that the user is ALWAYS taken to the dashboard, even if they are a new user. If they were a newUser and logged in, upon clicking the back button the user is navigated to the expected welcome new user activity.
on top of this, several instances of the activity are sometimes launched.
I just wondered if anyone could explain why this was happening, and what am I failing to see?
If clarity is needed please say so, I'll do my best to update the post.
Many thanks in advance.
From experimentation and further reading:
The addValueEvent listener is called for every value changed, therefore when setting the singleSnapShot to false this was in affect changing the value, thus the listener was being called again. Instead the correct approach was to user a Listener for a single value event.
When swapped for this method, the solution works as expected.

How to retrieve data from Firebase database without listeners?

Right now I'm developing an android app, and I just started to work with Firebase.
My question is: How can I retrieve data from the firebase database, without use listeners ?
In my game, I'm saving the high scores of all the users, and I need to take the data from the database when user go into "leader-boards" page.
I saw some solutions, which is not good for my case.
One of them is:
mRef.addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
String value = dataSnapshot.getValue(String.class);
}
#Override
public void onCancelled(FirebaseError firebaseError) {
}
});
This solution is not good for me, because I cant afford to my app to go into the server every time high score of user is changing, because if I would have 20,000 people who playing at the same time, then the game will stuck.
so I cant use this listener, because it will make the game very slow.
My goal is to to find a way to change the high score, without alerting the other people who is currently playing the game, I mean that I need to update every user score for his own, and when user go to the "leader boards" page, only then I will go to the server.
what is the right solution here ?
Or can I use this listener in another way?
If my question is not clear, then ask me in the comment please.
Thank you !!
my lines:
public static void setUserHighScoreToServer(Context context,boolean isClassic,int scoreNum)
{
com.firebase.client.Firebase mRef;
mRef= new com.firebase.client.Firebase("...");
String name = InternalStorage.getUserName(context);
String classic = "";
if(isClassic)classic="Classic";
else classic="Arcade";
com.firebase.client.Firebase mRefChild = mRef.child(name+classic);
mRefChild.setValue(String.valueOf(scoreNum));
}
This is the OFFICIAL way to retrieve data once without listening for data changes.
// Add all scores in ref as rows
scores.addListenerForSingleValueEvent( new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot snapshot) {
for (DataSnapshot child : snapshot.getChildren()) {
...
}
}
}
more information here: https://firebase.google.com/docs/reference/android/com/google/firebase/database/DataSnapshot
If you donĀ“t need to update on real time, you can always do a Rest api call to your database.
Just do a GET call to retrieve your data
https://[PROJECT_ID].firebaseio/[path].json
and you are good to go
You can also update or create new keys using rest api calls.

Categories

Resources