onDataChange is getting called twice in android Firebase - android

Here is My simple query for firebase data using timestamp in android app
Query recentStaticJobQuery = reference.child(AppConstants.WORKINDIA_JOBS)
.child(AppConstants.WORKINDIA_STATIC_JOBS)
.orderByChild(AppConstants.TIMESTAMP)
.startAt(lastStaticJobSyncTime);
recentStaticJobQuery.addListenerForSingleValueEvent
(staticJobDownloadListener);
ValueEventListener staticJobDownloadListener = new ValueEventListener() {
#Override
public void onDataChange(final DataSnapshot dataSnapshot) {
Log.i("Firebase", "Called")
}
#Override
public void onCancelled(DatabaseError databaseError) {
Log.i("Firebase", "onCancelled")
}
};
How to avoid onDataChange to get called twice in android Firebase?

There are 2 scenarios where this may happen:
onDataChange is called twice in case you have enabled offline persistence. Once with the stale offline value and again with the updated value in case it has changed.
onDataChange is called multiple times in case you have not removed the listener properly and are creating a new instance of your listener in your activity every time you open it.
Scenario 2 is easy to fix. You can maintain local references of your firebase reference and listener, than you can do a ref.removeListener(listener) in onDestroy of your Activity. Scenario 2 is difficult to fix and you have 2 possible remedies:
Disable offline persistence in case you always want the updated latest value.
Do a runnable.postDelayed(callbackRunnable, 3000); to wait for the latest value for 3 seconds before updating the views or whatever you want to update.

Use SingleEventListener instead of ValueEventListener Like this
Firebase ref = new Firebase("YOUR-URL-HERE/PATH/TO/YOUR/STUFF");
ref.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) {
}
});

Replace your query by adding endAt() like below. It will help you.
Query recentStaticJobQuery = reference.child(AppConstants.WORKINDIA_JOBS)
.child(AppConstants.WORKINDIA_STATIC_JOBS)
.orderByChild(AppConstants.TIMESTAMP)
.startAt(lastStaticJobSyncTime).endAt(lastStaticJobSyncTime+"\uf8ff");

I found out the reason for this issue.
Attaching and detaching the listeners were fine. Add listener getting called only once. But still child changes and value changes were getting called twice.
I noticed that the 2nd trigger was always about 320ms after the first one. The data was same in both the callbacks, except for the fields with ServerValue.TIMESTAMP. These fields also had about 320ms increase in the 2nd trigger.
The data I was writing to the firebase database was containing the ServerValue.TIMESTAMP as it stores the server's current timestamp in that field, and I was using it to filter the data.
On removing this field from the object I'm writing to database; the listeners are getting called only once.
Here is my code that caused 2 triggers:
My class
#Entity
class Route {
#PrimaryKey
var id: String = ""
#Exclude
get() {
return field
}
#Exclude
set(value) {
field = value
}
....
....
#Ignore
var z: Any? = null
}
In the viewModel
fun addRoute(routeName: String){
viewModelScope.launch(Dispatchers.IO) {
if(!repository.isRouteExists(routeName)){
val route = Route()
route.name = routeName
route.addedBy = uid
route.z = ServerValue.TIMESTAMP
repository.insertRouteToFirebase(route)
}
}
}
In the repository
fun insertRouteToFirebase(route: Route){
val database = Firebase.database
val myRef = database.getReference("routes")
val routeId = myRef.push().key
if(routeId != null){
route.id = routeId
myRef.child(routeId).setValue(route)
}
}
Logs when using ServerValue.TIMESTAMP (Notice the 300ms difference in log time and field z in data):
2022-09-11 11:38:05.880 23911-23911/com.slvt.vikku.sharadhamohotsav D/MY_FIREBASE: onDataChange: DataSnapshot { key = routes, value = {-NBf9NKGpmwWZDfp9uLp={addedBy=VLdrj1JbJhb1T9GXBm9WONhs5l03, name=new route , z=1662876484946}} }
2022-09-11 11:38:06.173 23911-23911/com.slvt.vikku.sharadhamohotsav D/MY_FIREBASE: onDataChange: DataSnapshot { key = routes, value = {-NBf9NKGpmwWZDfp9uLp={addedBy=VLdrj1JbJhb1T9GXBm9WONhs5l03, name=new route , z=1662876485282}} }
So the solution to this must be found out. This is just the cause of the issue.
On changing
route.z = ServerValue.TIMESTAMP
to some other number like
route.z = 791456
the listeners were getting called only once.

Related

How to set activity layout based on user type / role?

I am working on an Android app with 2 types of users (doctors and patients), and I want each type to have their own UI. For eg, doctors must see ' Add days off' and patients must see ' Book appointment' . Somehow I don't get anywhere with whatever I try.
I also use Firebase Auth and Realtime Database which makes the user type retrieval kinda tricky. So far I've tried a lot of Async classes, methods, variables, shared preferences, retrieving data while on launcher splash screen.
The best I got is getting the user to login, it shows the good layout, then I start the app again and it shows the wrong layout. Somehow I noticed it just works on the second run, but not always so the behaviour is unpredictable to me at least. But at least the user type from the database is retrieved.
I have a class that extends Application, which checks if there's an user authenticated and then redirects the user to either LoginActivity, or MainMenuActivity.
I have created a method that retrieves the Firebase Auth user data from Realtime Database, looping through both Doctors and Patients 'children' until it finds the current user email and gets its type. Since Realtime Database is asynchronous, the methos gets an interface as an argument, and after the loop, the I call the interface's method, which sets a static boolean variable (isUserDoctor).
Before setting the content view (with 2 possible layouts), I call the function described before and it works the way I first mentioned, which is not good.
The method that retrives data
public void getUserType(final DataStatus dataStatus) {
currentUser = FirebaseAuth.getInstance().getCurrentUser();
currentUserEmail = currentUser.getEmail();
databaseReference = FirebaseDatabase.getInstance().getReference("Users");
databaseReference.addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(#NonNull DataSnapshot dataSnapshot) {
currentUserType.clear();
FirebaseManager.isUserDoctor = false;
DataSnapshot allDoctors = dataSnapshot.child("Doctors");
DataSnapshot allPatients = dataSnapshot.child("Patients");
for (DataSnapshot ds : allDoctors.getChildren()) {
if (currentUserEmail.equals(Utils.decodeUserEmail(ds.getKey()))) {
currentUserType.add(ds.child("userType").getValue().toString());
} else {
for (DataSnapshot dsPacient : allPatients.getChildren()) {
if (currentUserEmail.equals(Utils.decodeUserEmail(dsPacient.getKey()))) {
currentUserType.add(dsPacient.child("userType").getValue().toString());
}
}
}
}
dataStatus.DataIsLoaded(currentUserType.get(0).toString());
}
#Override
public void onCancelled(#NonNull DatabaseError databaseError) {
}
});
The interface
public interface DataStatus {
void DataIsLoaded(String userType);
}
The method's call in Main Menu
FirebaseManager.getInstance().getUserType(new DataStatus() {
#Override
public void DataIsLoaded(String userType) {
if ("doctor".equals(userType))
FirebaseManager.isUserDoctor = true;
else
FirebaseManager.isUserDoctor = false;
}
});
if (FirebaseManager.isUserDoctor)
setContentView(R.layout.activity_main_menu_doctor);
else
setContentView(R.layout.activity_main_menu);
So if anyone has any ideas about how to show the proper layout and allow functions based on user role/type please share. What I basically need is to retrieve the userType from the current email just in time to set a variable needed throughout the whole app in order to hide/show certain views.

Retrieve String out of addValueEventListener Firebase

I want to receive a string from addValueEventListener() method I use to resell the data from the database Firebase. The data arrive correctly.
But when certain to get the string out of that method to use it in another, it returns nothing.
You have tips?
I already tried putExtras and also create a method on purpose but it did not work.
final DatabaseReference mPostReference = FirebaseDatabase.getInstance().getReference().child("user-daily").child(getUid()).child("2017-Year");
mPostReference.addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
final ArrayList<String> labels = new ArrayList<String>();
for (DataSnapshot data : dataSnapshot.getChildren()){
final DailyItem dailyItem = data.getValue(DailyItem.class);
labels.add(dailyItem.mese);
}
title.setText(labels.get(position));
a = title.getText().toString();
}
#Override
public void onCancelled(DatabaseError databaseError) {
Toast.makeText(view.getContext(),"database error",
Toast.LENGTH_SHORT).show();
}
});
//this return null... why?
String title = a;
The data is loaded from Firebase asynchronously. By the time you run title = a, the onDataChange method hasn't been called yet. Set some breakpoints in a debugger to verify this, it's key to understanding how asynchronous loading works.
The solution is to reframe your problem from "first get the object, then do blabla with the title" to "start getting the object; once the object is available, do blabla with the title".
In code this translates to:
final DatabaseReference mPostReference = FirebaseDatabase.getInstance().getReference().child("user-daily").child(getUid()).child("2017-Year");
mPostReference.addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
final ArrayList<String> labels = new ArrayList<String>();
for (DataSnapshot data : dataSnapshot.getChildren()){
final DailyItem dailyItem = data.getValue(DailyItem.class);
labels.add(dailyItem.mese);
}
title.setText(labels.get(position));
// Do blabla with the title
String title = title.getText().toString();
}
#Override
public void onCancelled(DatabaseError databaseError) {
Toast.makeText(view.getContext(),"database error",
Toast.LENGTH_SHORT).show();
}
});
Many developers new to Firebase (and other modern web APIs, as they all work this way) struggle with this problem. So I recommend you also check out their questions and answers:
Cannot access firebaseObjectObservable outside of set
Android Firebase get value of child without DataChange
Value of a global variable is reset after it is initialised in ValueEventListener
can't get values out of ondatachange method
ArrayList not updating inside onChildAdded function
Setting Singleton property value in Firebase Listener
and most others in this list of search results
In order to retrieve the string from method addValueEventListener in viewmodel or any other network call, it is recommended to use the either MutableLiveData<T> or LiveData<T> and observe the same in your activity. Observer will observe the changes, and as soon as string got filled up, the observer method will automatically give you string which you are looking.
You need to create reference variable for the LiveData<T> reference_variable wherever your addValueEventLister is located and set its value in your addValueEventListener.
And then in your viewmodel create the returning value function like below...
Observe this function in your activity and you will have your string.
public MutableLiveData<TotalRunsWicketsAndData> getDisplayableDetails() {
return observableLiveData;
}
I am using MutableLiveData here.
This is a trick which does it. It would be easy to do so if you have less data to retrieve from ValueEventListener.
Inside the onDataChange(), use a setText to set the required value in it. Keep the visibility of this text view as "Gone". Then retrieve using getText outside the ValueEventListener.
You can retrieve the whole list by using GenericTypeIndicator. Follow the official guide on here

Getting the relevant key from ValueEventListener

I'm building an app that is basically a table for soccer that updates every time I change it in my DB.
I'm using Firebase in this way:
I'm trying to use an addValueEventListener, but I don't know which one of the values is the one that changes (dif,score,points...) therefore I don't which field in my table I should update.
public class table extends AppCompatActivity {
Button insert;
TextView name, games, win, lost, tie, score, dif, points;
Team A;
DatabaseReference mRootRef = FirebaseDatabase.getInstance().getReference();
DatabaseReference FootballRef = mRootRef.child("Football");
DatabaseReference HouseARef = FootballRef.child("HouseA");
protected void onStart() {
super.onStart();
ValueEventListener postListener = new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
// Get Post object and use the values to update the UI
String key = dataSnapshot.getKey() ;
Toast.makeText(table.this,key, Toast.LENGTH_SHORT).show();
// ...
}
#Override
public void onCancelled(DatabaseError databaseError) {
// Getting Post failed, log a message
// ...
}
};
HouseARef.addValueEventListener(postListener);
}
}
See here, when let's say I'm updating the name of housaA, I want to get back the key that got updated (in my case, name), but when I'm using
dataSnapshot.getKey() the Toast I'm getting back is HouseA and not name.
You won't be able to do this using a valuelistener.
Instead put a childeventlistener on the houseA.
Now inside onChildChanged, you should get the child node under houseA which has changed as a datasnapshot and from this snapshot, you can get the key which has changed

Android - Persist data retrieved from Firebase

I have an activity and a model called CourseDetails.
String getData;
DatabaseReference mRef = FirebaseDatabase.getInstance().getReference().child("courses").child("Business");
mRef.addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
CourseDetails c = dataSnapshot.getValue(CourseDetails.class);
getData = c.getCourseName();
}
#Override
public void onCancelled(DatabaseError databaseError) {
}
});
textview1.setText(getData);
Using above code throws NullPointerException at last line above. But if I put textview1.setText(getData) into the ValueEventListener, under getData = c.getCourseName(), the data can be displayed correctly.
Methods I found working are using SharedPreferences or setting data from a method such as public void display(String data) { textview1.setText(data); }. But what are the other ways to keep the retrieved data even if the data is outside ValueEventListener?
For instance I want to persist the data added into an ArrayList.
ArrayList<String> listData;
DatabaseReference mRef = FirebaseDatabase.getInstance().getReference().child("courses").child("Business");
mRef.addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
for (DataSnapshot ds : dataSnapshot.getChildren()) {
CourseDetails c = dataSnapshot.getValue(CourseDetails.class);
String code = c.getCourseCode();
String name = c.getCourseName();
String CodeName = code + " " + name;
listData.add(CodeName);
}
}
#Override
public void onCancelled(DatabaseError databaseError) {
}
});
// data in ArrayList should be able to display here
StringBuilder builder = new StringBuilder();
for (String s : listData) {
builder.append(s + "\n");
}
textview1.setText(builder.toString());
How to achieve this kind of persistence?
As per my understanding, Firebase will notify all it's data listener attached to specific references (database references wherever the addValueEventListener is added) when those specific data gets modified. That is when
onDataChange will be called, when there is modification of the data at those database references,
(besides modification the method will always be called first time).
And this happens
asynchronously, so in the first case where null occurs because we don't know whether data is retreived from Firebase and
as far as I know, Android's main thread cannot be put on hold or pause until we retreive the data that's why we use Asynchronous tasks in Android.
So, I think the best way to do specific updates or task on data change is within onDataChange method. So, like you stated it could be
done by making those changes within onDataChange itself or by calling some other method from onDataChange.
Or, if you are using
adapter then, notifying adapter about the change within onDataChange. Also, you can take a look at other choice i.e. FirebaseRecyclerAdapter then,
it will handle the update automatically without any extra effort.

Firebase database not retrieving data

This is my first time using firebase and I have a few methods all contained in the same class that don't seam to be working as they should. nameExists always returns false, even when I can see in the console that the value stored at name is not null. userStatus is a DataSnapshot field in this class.
public Boolean nameExists(String name){
if (getUserStatus(name) != null){
return true;
} else{ return false; }
}
public String getUserStatus(String name){
DatabaseReference tmpRef = FirebaseDatabase.getInstance().getReference().child("userStatuses").child(name);
ValueEventListener v = new statusesListener();
tmpRef.addValueEventListener(v);
tmpRef.removeEventListener(v);
return userStatus;
}
private class statusesListener implements ValueEventListener{
public void onDataChange(DataSnapshot snap){
userStatus = (String) snap.getValue();
}
public void onCancelled(DatabaseError error){
}
}
Thanks for the help
You have overlooked the asynchronous nature of the ValueEventListener callbacks. When a listener is added, it typically requires that the value be fetched from the Firebase server before it can be provided by the onDataChange() callback. The callback fires asynchronously, usually many milliseconds after the listener has been added.
In your code, getUserStatus() adds the statusesListener, then removes it and returns userStatus. All of that occurs before onDataChage() has fired, so the value of userStatus is unchanged.
Also, adding and immediately removing a ValueEventListener is not the typical use case and, given the listener's asynchronous nature, may cause it to never fire. It is better to add the listener with Query.addListenerForSingleValueEvent(), which gives you the value one time and does not require the listener to be removed.

Categories

Resources