I have a list of string and this list contains node names in Firebase database.
And I have a custom sort method therefore I want to get the all of the keys for each node in my custom list without using Firebase` query sorting functions.
And because of it I need all data to be retrieved, so adding to list as it retrieves some data is not an option.
However the problem starts with asynchronous structure of Firebase.
I iterate through every node name in my custom list and under that loop I create another loop in Firebase` thread ( addListenerForSingleValueEvent ) to retrieve all keys.
But it works asynchronously. I've tried to change it to synchronous by using Thread and Semaphore but it didn't work.
I've also used custom interface (FirebaseDataRetrieveListener) to indicate when the loop in valueEventListener finishes but since valueEventListener instantly return this is not possible without pausing the thread.
If Task's can be used in this situation, how it could be, or are there any other solutions?
private void getKeysFromNodeList(final FirebaseDataRetrieveListener listener) //TODO [BUG] when first for loop initializes it ignores the seperate thread in the inner anonymous class so it'll be already finished it's cycle before Firebase` loop starts...
{
listener.onStart();
final DatabaseReference databaseRef = firebaseInstance.rootRef.child(context.getString(R.string.databaseref));
for (iterator = 0; iterator < nodeList.size(); iterator ++)
{
Query query = databaseRed.child(nodeList.get(iterator));
query.addListenerForSingleValueEvent(new ValueEventListener()
{
#Override
public void onDataChange(#NonNull DataSnapshot dataSnapshot)
{
for (DataSnapshot snap : dataSnapshot.getChildren())
{
iterator++;
String key= snap.getKey();
// I've done my jobs with given key here
// if got the last key from node and iterated the last node
if (firebaseIterator== dataSnapshot.getChildrenCount() - 1 && iterator == nodeList.size() - 1)
{
firebaseIterator= 0;// reset the iterators
iterator= 0;
listener.onSuccess();
break;
}
// if got the last key from node
else if (firebaseIterator== dataSnapshot.getChildrenCount() - 1)
{
firebaseIterator= 0; // reset the iterator
break;
}
}
}
#Override
public void onCancelled(#NonNull DatabaseError databaseError)
{
listener.onFailed(databaseError);
return;
}
});
}
;
}
}
FirebaseDataRetrieveListener.java
public interface FirebaseDataRetrieveListener
{
public void onStart();
public void onSuccess();
public void onFailed(DatabaseError databaseError);
}
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. Pausing a thread might solve your problem but only partial. and it's not recommened at all.
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 some time 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, the values that are coming from the database will not have been populated from the callback yet.
A quick solve for this problem would be to use those values only inside the callback, otherwise I recommend you see the last part of my anwser 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.
As a conclusion, there is no way to turn an asynchronously API as Firebase is into a synchronous one.
To solve this problem I made my method recursive and used a listener.
In my solution we are gonna keep track on our how many times we called the recursive method, and how many time Firebase looped through to keys of given node in order to detect whether we reach to end of node and we looped all the nodes in the list.
Declare 2 iterator and 1 listener in your class` scope eg.
private RecursiveListener listener;
private long firebaseIterator = 0;
private int recursiveIterator = 0;
public interface getKeysFromNodeListListener
{
void onFinished();
}
We are gonna use the recursive listener in order to get notified whether our recursive function has finished.
Before calling your method define the listener and override onFinished().
onFinished gonna be called when we both looped through all nodes and their keys.
private void getKeysFromNodeList(final getKeysFromNodeListListener listener)
{
final DatabaseReference databaseRef = firebaseInstance.rootRef.child(database_reference);
if (recursiveIterator < nodesList.size())
{
Query query = databaseRef.child(nodesList.get(recursiveIterator));
query.addListenerForSingleValueEvent(new ValueEventListener()
{
#Override
public void onDataChange(#NonNull DataSnapshot dataSnapshot)
{
for (DataSnapshot snap : dataSnapshot.getChildren())
{
firebaseIterator++;
//Do you job with the current key
String key = snap.getKey();
keysList.add(key);
// if got the last key from key and iterated the last node
if (firebaseIterator == dataSnapshot.getChildrenCount() && recursiveIterator == nodeList.size() -1)
{
firebaseIterator = 0;// reset the iterators
recursiveIterator = 0;
return;
}
// if got the last key from current node
else if (firebaseIterator == dataSnapshot.getChildrenCount() )
{
recursiveIterator++; // increase the recursive iterator because we looped through all the keys under the current node
firebaseIterator = 0; // reset the recursiveIterator because we gonna need to loop again with default value
getKeysFromNodeList(listener); // call the same method
}
}
}
#Override
public void onCancelled(#NonNull DatabaseError databaseError)
{
Log.e(TAG, "Firebase query failed " + databaseError);
return;
}
});
}
}
And when using the method,first create a listener and override the onFinished() and then call the method.
listener = new getKeysFromNodeListListener()
{
#Override
public void onFinished()
{
// Do whatever you gonna do after you got all the keys from all nodes.
}
};
getKeysFromNodeList(listener);
Related
I often find myself writing this piece of code again and again in multiple activities when using firebase realtime db:
ValueEventListener v =new ValueEventListener() {
#Override
public void onDataChange (#NonNull DataSnapshot dbSnapshot){
String ourKey="";
String ourValueID="";
for (DataSnapshot childSnap : dbSnapshot.getChildren()) {
String childKey = childSnap.getKey();
if (childKey == null) {
//do some stuff 1 // and break/Continue/return
}
//or we can directly do something here, as we already assured key is present
else if(childKey.equals(ourKey)){
//do some stuff 2 // and break/Continue/return
MyClass myClass =childSnap.getValue(MyClass.class);
if(myClass==null){
//do some stuff 3 // and break/Continue/return
}
else if(myClass.getID().equals(ourValueID)){
//do some stuff 4 // and break/Continue/return
}
else {
//do some stuff 5 // and break/Continue/return
}
}
else {
//do some stuff 6 // and break/Continue/return
}
}
}
#Override
public void onCancelled (#NonNull DatabaseError databaseError){
//do some stuff 7
}
};
although this is suppose to be how firebase works, it makes my code a lot more unreadable and difficult to debug. what could be a good approach to use these callbacks in a way, that i write this code once and de-clutter my code base?An example would be great.
Inside onDataChange(), you can just call a method:
ValueEventListener v =new ValueEventListener() {
#Override
public void onDataChange (#NonNull DataSnapshot dbSnapshot){
String ourKey="";
String ourValueID="";
retrieveDataFromFb(dbSnapshot);
public void retrieveDataFromFb(DataSnapshot dataSnapshot){
for (DataSnapshot childSnap : dbSnapshot.getChildren()) {
String childKey = childSnap.getKey();
if (childKey == null) {
//do some stuff 1 // and break/Continue/return
}
else if(childKey.equals(ourKey)){
MyClass myClass =childSnap.getValue(MyClass.class);
}
}
From what I understand you want to store all DB methods in a separate class so that you can reuse these methods which would make the code look cleaner and you are trying to get callback values when they get returned from firebase.
There can be many ways to handle callbacks for events what I recommend is to use an interface it will modularize your code and make it look cleaner, so what you can do is to store the DB methods in a separate class (say FirebaseDB), create your methods there and use an interface to get the callbacks. An example on how you can do it:-
Create an Interface either in the class or separate from the class
public class FirebaseDB {
//This is your interface
public interface DBCallbacklistner {
void onCallback(Map<String, Object> keyMap);
}
public void getkeys(String any_value_you_need_to_pass, DBCallbacklistner dbCallbacklistner){
//I have used a different method here you can use your releveant method here
database.somemethod(any_value_you_need_to_pass, new EventListener<DocumentSnapshot>() {
#Override
public void onEvent(#Nullable DocumentSnapshot documentSnapshot) {
//Suppose you receive the callback here
if(documentSnapshot.exists()){
Map<String, Object> keysMap = (HashMap<String, Object>) documentSnapshot.getData();
//Pass the callback in your interface
dbCallbacklistner.onCallback(keysMap);
}
}
});
}
}
Use that interface wherever you want
Using the function from the class call that interface and use the values
mFirebaseDBObject.getkeys(value, new FirebaseDB.DBCallbacklistner() {
#Override
public void onCallback(Map<String, Object> keyMap) {
if (keyMap != null) {
//Use your keymap here
}
}
});
One more thing I want to point out is that If there are too many callbacks for different calls, I suggest to make separate interfaces based on logical seperation of callbacks.
Because if there are many callbacks in a single interface you would have to override each one of them, whether you require it or not.
For the time being, i am using the following approach:
Suppose my firebase db consists of a list of objects which can be deserialised to the following format:
class MyClass{
public String myClassUniqueID;
... other attributes;
}
For the db i will be handling all value event listener's lifecycle in my own activity(i.e attaching to the db refernce via dbRef.addValueEventListener(dbListener); or dbRef.removeEventListener(dbListener);, But the process of creating this dbListener and passing it the neccessary tasks to be done would be managed in the following utility function :
public interface DbListenerActions {
void onMyClassObjFound(#NonNull MyClass matchedObj);
default void onMyClassObjNOTFound() {
}
}
public static ValueEventListener getMyClassObjectFinderListener(String id, DbListenerActions actions) {
Log.e(TAG, "onDataChange: our id:" + id);
ValueEventListener dbListener = new ValueEventListener() {
#Override
public void onDataChange(#NonNull DataSnapshot dbSnapshot) {
for (DataSnapshot currChildSnap : dbSnapshot.getChildren()) {
String currChildKey = currChildSnap.getKey();
MyClass currChildValue = currChildSnap.getValue(MyClass.class);
if (currChildKey == null) {
Log.e(TAG, "onDataChange: currChildKey is null. continuing");
continue;
}
if (currChildValue == null) {
Log.e(TAG, "onDataChange: currChildValue is null.continuing");
continue;
}
if (currChildValue.myClassUniqueID.equals(id)) {
Log.e(TAG, "onDataChange: currChildValue id matches our id ");
Log.e(TAG, "onDataChange: performing action and RETURNING(i.e getting out of this callback)");
//do stuff here
actions.onMyClassObjFound(currChildValue);
return;
} else {
Log.e(TAG, "onDataChange: current obj DOES NOT matches our id. continuing");
Log.e(TAG, "onDataChange: current object ID :" + currChildValue.myClassUniqueID);
Log.e(TAG, "onDataChange: --------------------------------------------------------------");
continue;
}
}
Log.e(TAG, "onDataChange: user not found, performing not found action" );
actions.onMyClassObjNOTFound();
}
#Override
public void onCancelled(#NonNull DatabaseError databaseError) {
}
};
return dbListener;
}
In this way, i am able to get the necessary log info that i want during my debugging and since there are only 2 possible actions that i want to perform, i get a lot more assurance on the working of my listener. 50 lines of necessary but redundant code from 11 activities combined to just 1 utility function!
Now all i need to write is this small , more easy to debug piece of code in each of my activity:
ValueEventListener dbListener=getMyClassObjectFinderListener("some_id", new DbListenerActions() {
#Override
public void onMyClassObjFound(#NonNull MyClass matchedObj) {
//callSomeFunction()
// callSomeOtherFunction(matchedObj)
//...
}
});
Since i made the onMyClassObjNOTFound(..) function default i don't even need to provide that unless i really want to perform some action there. So this whole thing is working nicely for me :D
I asked on twitter about this thing too, somebody told me that an abstract class could also be used for such thing. I didn't got to research more there, but if someone knows about that approach too, then let me know!
So I am using Firebase Realtime Database and I want to remove a listener as soon as a certain criteria is met. Here is my code:
final DatabaseReference forRequests = FirebaseDatabase.getInstance().getReference(Common.requests + "/" + FirebaseAuth.getInstance().getUid());
listenForRequests = forRequests.addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
pickuprequest.riderUID = (String) dataSnapshot.child(Common.riderUID).getValue();
if (pickuprequest.riderUID != null) {
forRequests.removeEventListener(listenForRequests);
showRequestOnMap(forRequests);
}
}
#Override
public void onCancelled(DatabaseError databaseError) {
System.out.println("The read failed: " + databaseError.getCode());
}
});
}
listenForRequests is a global variable. Just wondering if this code will work, or if there are any better solutions to this as I feel like it is very hacky.
I can't really tell what exactly your condition is really trying to express, but if you want just a single value a single time from the database (without listening to its changes over time), you can simply use addListenerForSingleValueEvent() to get a single snapshot of a node in the database.
If you're waiting for a value to appear that wasn't previously there, and you want to stop listening at the time it appears, what you're doing is fine. But you might want to listen more closely to the child of interest instead of its parent.
Your code looks pretty idiomatic to me when you want to wait for a specific value.
In fact, the code in my gist on waiting for an initial value is pretty similar:
mListener = ref.addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot snapshot) {
if (snapshot.exists()) {
System.out.println("The value is now "+snapshot.getValue());
ref.removeEventListener(mListener);
}
}
...
I am developing an android app using Firebase Realtime Database.
My addValueEventListener for a specific reference runs whenever there is a data change in the database. It also runs whenever the app starts. These make sense to me, but this even runs when I return to my app from a different activity. For example, it runs when I go to the HOME screen of my phone and go back to this app, the addValueEventListener runs.
This is a problem for me because I am implementing a vibration whenever there is a data change in my app. I put a global int variable to prevent vibration on the first time i get to the app.
It works like:
matchReference.addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
if(notificationCounter > 0) notifyGoals(getView());
clearCurrentList();
notificationCounter++;
// Fires every single time the channelReference updates in the
}
adapter.notifyDataSetChanged();
}
#Override
public void onCancelled(DatabaseError databaseError) {
}
});
I initialize int notificationCounter = 0 outside of this function so that it gets reset to 0 whenever I re-run this app. But, whenever I go to a different activity on my phone (HOME Screen or LOCK screen) and return to this screen, it runs the addValueEventListener before the int notificationCounter = 0 initialization. How can I solve this problem?
This is happening because onDataChange() method is called asynchronous which means that this method does not wait to get the data from your database and is called even before. To solve this, you have 2 choices. One would be to move the declaration of your notificationCounter int inside the onDataChange() method or second, to dive into the asynchronous word and use my answer from this post.
Just remove your valueListener in activity pause
private HashMap<DatabaseReference, ValueEventListener> hashMap = new HashMap<>();
private ValueEventListener valueEventListener;
mChatReference.child(groupID).addValueEventListener(valueEventListener=new ValueEventListener() {
#Override
public void onDataChange(#NonNull DataSnapshot dataSnapshot) {
}
#Override
public void onCancelled(#NonNull DatabaseError databaseError) {
}
});
#Override
public void onBackPressed() {
hashMap.put(mChatReference.child(groupID), valueEventListener);
removeValueEventListener(hashMap);
finish();
}
public void removeValueEventListener(HashMap<DatabaseReference, ValueEventListener> hashMap) {
for (Map.Entry<DatabaseReference, ValueEventListener> entry : hashMap.entrySet()) {
DatabaseReference databaseReference = entry.getKey();
ValueEventListener valueEventListener = entry.getValue();
databaseReference.removeEventListener(valueEventListener);
}
}
I have a firebase database from which I save and retrieve data from, to and from. I know how datasnapshot works inside an addValueEventListener. The problem is that this is only called or triggered when the firebase database detects change in its data. I only want to access data and read it to be able to store it in an arraylist or the same thing.
I have a code like this:
public void foo(){
DatabaseReference x= FirebaseDatabase.getInstance().getReference().child("x");
reservations.addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
String userID = client.getId();
for(DataSnapshot snap : dataSnapshot.getChildren()){
if(snap.child("someId").equals(someId)) number++;
if(snap.child("userID").getValue().equals(client.getId())){
isAlreadyReserved = true; // if user has already reserved the item
alreadyReserved();
break;
}
Log.e("isAlreadyReserved: ", isAlreadyReserved+"");
numberOfReservations++;
}
}
#Override
public void onCancelled(DatabaseError databaseError) {
}
});
if(isAlreadyReserved) {
alreadyReserved();
}
else if(number == numberOfCopies){
// material is no longer available
OtherActivity.showMaterialUnavailable();
}
else{
Reservation reservation = new Reservation();
reservation.setBookId(this.bookId);
reservation.setResID((numberOfReservations+1)+"");
reservation.setUserID(client.getId());
String key = reservations.push().getKey();
reservations.child(key).setValue(reservation);
Log.e("Reservations: ", "reserve successful");
AlertDialog.Builder builder = new AlertDialog.Builder(this.context);
builder.setTitle(R.string.reservationSuccess_title)
.setMessage(R.string.reservationSuccess_body);
AlertDialog dialog = builder.create();
dialog.show();
}
}
You can see that inside onDataChange I only count materials and set some flags, which I can supposedly do outside the ValueEventListener.
But I notice that this is faulty because onDataChange is called only when writing to the Firebase database occurs. Which should not be the case.
What can I do to loop through the values inside the DatabaseReference x without calling onDataChange, or without using DataSnapshot?
You cannot loop inside a DatabaseReference without using a listener. When we are talking about Firebase, we are talking only about listeners. So in order to get those values, you need to use a listener and than get the data out from the dataSnapshot.
What i think your problem is in your case, is that onDataChange method is called in an asynchronously way. This means that everything you are doing outsite this method is actually executed before onDataChange method has been called. So in order to understand what is actually going on, please see this post and this post. Reading this posts, will teach you how to query data in Firebase and how to retrieve data from Firebase asynchronously.
Hope it helps.
In order to get the values of DatabaseReference x, you should use addListenerForSingleValueEvent
x.addListenerForSingleValueEvent(new ValueEventListener()
{
#Override
public void onDataChange(DataSnapshot dataSnapshot)
{
//do something
}
#Override
public void onCancelled(DatabaseError databaseError)
{
//do something
}
});
as mentioned in the firebase documentation:
public void addListenerForSingleValueEvent (ValueEventListener
listener)
Add a listener for a single change in the
data at this location. This listener will be triggered once with the
value of the data at the location.
Currently, I am using firebase Realtime Database. Hence, my data changes come from another thread. Hence, I have no control on when the fresh data update comes over. How do I then know when to call to refresh my UI?
This is my implementation of a swipe to delete in a RecyclerView.
public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {
int pos = viewHolder.getAdapterPosition();
mListRoute.get(pos).removeRoute();
refreshUI();
}
This is the removeRoute() method found in my Route class
public void removeRoute() {
//Delete all the Route Instructions
DatabaseReference mRef = FirebaseDatabase.getInstance().getReference()
.child("Routes")
.child(routeId)
.child("Route Instructions");
mRef.addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
for (DataSnapshot routeInstruc : dataSnapshot.getChildren()) {
routeInstruc.getValue(RouteInstructions.class)
.removeRouteInstructions();
}
DatabaseReference mRef2 = FirebaseDatabase.getInstance().getReference()
.child("Routes")
.child(routeId);
mRef2.removeValue();
}
#Override
public void onCancelled(DatabaseError databaseError) {
}
});
}
As you can see, the onDataChange() is called by another thread. Which means i do not know when to call my refreshUI() method.
I think i might be able to use a Looper but how do i fit that in the Route class?
Thanks
What you are looking for are callbacks.
Callbacks are practically mandatory when dealing with asynchronous calls, because when you call an asynchronous task, you are basically asking a worker thread to work for you.
It may take 1 second, 10 seconds, 10 minutes, etc, and you can not know for sure. What you can do is delegate that same worker thread and tell her "hey, reply back when you finish the task I gave you".
Enter the callbacks!
You can check for more documentation regarding callbacks here
Say that you have your query defined with the ValueEventListener
query.addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot data) {
}
}
What you should do is have a callback method that replies back as soon as the query listener returns a value (in other words, when your query is executed).
So, have a method like 'onResponseReceivedFromFirebase' and implement it on the callback
public class MyActivity extends Activity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
// Implement your callback here with the parameters you want (for instance, I used a String there)
public void onResponseReceivedFromFirebase(String argument){
Log.d(MyActivity.class,argument);
}
....
query.addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot data) {
onResponseReceivedFromFirebase("the response arrived!");
}
}
...
}
#Edit
Base on your updated code, I would proceed with the following
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
for (DataSnapshot routeInstruc : dataSnapshot.getChildren()) {
routeInstruc.getValue(RouteInstructions.class)
.removeRouteInstructions();
}
DatabaseReference mRef2 = FirebaseDatabase.getInstance().getReference()
.child("Routes")
.child(routeId);
mRef2.removeValue();
// Implement the callback here
MyActivity.this.onResponseReceivedFromFirebase("We have received a response from dataChanged");
}
#Edit 2 : On Frank van Puffelen's remark, the onDataChange method already runs on the Main Thread, thus allowing you to change any element on the UI.
Very import : If the processing payload of the outcoming data is considerably large, you should pass that same processing into another thread (e.g. An AsyncTask) to avoid making your app non-responsive.
While the Firebase client handles all network and disk IO on a separate thread, it invokes the callback to your code on the main thread. So you can update the UI straight from onDataChange(), without having to worry about the thread yourself.
In fact, all examples in the Firebase documentation update the UI from their onDataChange() (or onChild...()) callbacks. One example from the database quickstart:
public void onDataChange(DataSnapshot dataSnapshot) {
// Get Post object and use the values to update the UI
Post post = dataSnapshot.getValue(Post.class);
mAuthorView.setText(post.author);
mTitleView.setText(post.title);
mBodyView.setText(post.body);
}