Firebase / Android - Wait for all thread finish for response - android

I am have the follow code:
public synchronized void next(final RoomListQueryResultHandler handler) {
this.setLoading(true);
roomList = new ArrayList<Room>();
this.database.child("members").child(this.mUser.getUid()).child("rooms")
.limitToFirst(this.mLimit)
.startAt(this.currentPage * this.mLimit)
.addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
RoomListQuery.this.setLoading(false);
//mListAdapter.setLoading(false);
if (!dataSnapshot.hasChildren()) {
RoomListQuery.this.currentPage--;
}
for (DataSnapshot ds : dataSnapshot.getChildren()) {
Room room = ds.getValue(Room.class);
//roomList.add(Room.upsert(room));
Room.getRoom(room.getId(), new Room.RoomGetHandler() {
#Override
public void onResult(Room room, customException e) {
if (e != null) {
// Error!
e.printStackTrace();
return;
}
roomList.add(room);
}
});
handler.onResult(roomList, (customException) null);
}
}
#Override
public void onCancelled(DatabaseError databaseError) {
handler.onResult((List) null, new customException(databaseError.toString()));
}
});
}
}
If they are see, I have two Handlers, at first I call a list of "rooms" from Firebase, and then for each one I get the detail in other query.
The problem is that the response is a empty list, since the function not wait for all query details to be executed for the rooms, so the variable roomList always returns empty.
Any idea what I can implement, or what other methodology to use to solve it?
Thank you very much!
Greetings.

Depending on how your application is structured, you might want to change the database design so that there is no need to perform an additional Firebase query for each room retrieved from the first query.
//mListAdapter.setLoading(false);
If you're creating a list view where each row is from the /members/<user_id>/rooms Firebase node, what are the minimum room attributes necessary to display that list? If it's just a few things like room name, photo url, owner, room_id, etc you might be better off duplicating those from the original source. Then clicking one of those rows can trigger the original additional Firebase query you had as part of Room.getRoom(room.getId(), new Room.RoomGetHandler() { ... });, to navigate to a new screen / display a modal with the full room details once retrieved.
Update
To address your comment about requiring the extra data, in that case, as part of the Room class I would include an extra boolean value _loadedDetails set initially to false. So that for rendering a room within the list, when _loadedDetails is currently false just display a loading spinner. That way you can still perform those additional queries and when completed, update the appropriate Room object within roomList based on the index. Something like this:
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
RoomListQuery.this.setLoading(false);
//mListAdapter.setLoading(false);
if (!dataSnapshot.hasChildren()) {
RoomListQuery.this.currentPage--;
}
int i = 0;
for (DataSnapshot ds : dataSnapshot.getChildren()) {
Room room = ds.getValue(Room.class);
roomList.add(room); // here instead
updateRoom(room, i);
i++;
}
handler.onResult(roomList, (customException) null);
}
...
// outside of the ValueEventListener
public void updateRoom(room, index) {
Room.getRoom(room.getId(), new Room.RoomGetHandler() {
#Override
public void onResult(Room room, customException e) {
if (e != null) {
// Error!
e.printStackTrace();
return;
}
room._loadedDetails = true; // make that publicly accessible boolean, or include a setter method instead
roomList.set(index, room);
}
});
}

Related

Placing Firebase value event callbacks in a single place

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!

Firebase search query not working on android

I'm implementing a search in my android app and I can't seem to make it work.
public void loadReleaseData(String name) {
mDatabase.child("releases")
.child("europe")
.child("data").orderByChild("game/name").startAt(name)
.endAt(name+"\uf8ff")
.addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
if (dataSnapshot.hasChildren()) {
Log.d(TAG, "New datasnapshot");
for (DataSnapshot data : dataSnapshot.getChildren()) {
_Release release = data.getValue(_Release.class);
if (release != null) {
// No platform filter set add all releases!
list.add(release);
if (release.getGame() != null) {
Log.d(TAG, "NAME: " + release.getGame().getName());
}
}
mUpcomingGamesAdapter.notifyDataSetChanged();
}
}
}
#Override
public void onCancelled(DatabaseError databaseError) {
}
});
}
When I remove startAt or endAt either of the two data is shown but not the correct data, but when they're both added in, nothing is show and my log "New datasnapshot" doesn't even get printed. I'm searching on the names of the games I have in my database.
My firebase database:
According to your comments, the reason your code was not working was because the value of the name variable that was passed to startAt() and endAt methods was incorrect.
The key for solving the problem is to pass as an argument to both method the exact same name that exist in the database, in this case you should search the name in lower case.

How do I check if specific child value exists in FireBase (Android)

I have some trouble trying to check if user information is stored already in the FireBase database.
Basically I'm trying to do something stupid like this:
"select user_name from user where user_id="+userID+"
And if the nickname exists it should make the boolean var isFirstTime = false and if it doesn't it should stay true. And after that it should show register box or not.
This is my db:
Firebase
And this is my code in onCreate method:
databaseReference = FirebaseDatabase.getInstance().getReference();
DatabaseReference dbRefFirstTimeCheck = databaseReference.child("User").child(user.getUid()).child("Nickname");
isFirstTime = true;
dbRefFirstTimeCheck.addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
if(dataSnapshot.getValue() != null) {
isFirstTime=false;
}
}
#Override
public void onCancelled(DatabaseError databaseError) {
}
});
if(isFirstTime) {
showNewUserBox();
}
else {
}
No matter what I do, the methor showNewUserBox() is being called. How do I get the data i need and check if it's there?
As others have commented, data is loaded from Firebase asynchronously. By the time you check isFirstTime, the data hasn't been loaded yet, onDataChange hasn't been run yet, so ifFirstTime will have its default value (false for a boolean).
All code that requires data from the database should be inside onDataChange (or invoked from within there). The simplest fix for your code is:
databaseReference = FirebaseDatabase.getInstance().getReference();
DatabaseReference dbRefFirstTimeCheck = databaseReference.child("User").child(user.getUid()).child("Nickname");
dbRefFirstTimeCheck.addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
if(dataSnapshot.exists()) {
showNewUserBox();
}
}
#Override
public void onCancelled(DatabaseError databaseError) {
throw databaseError.toException(); // don't ignore errors
}
});
Also see some of the many questions about asynchronous loading from Firebase, such as getContactsFromFirebase() method return an empty list (or this quite old classic: Setting Singleton property value in Firebase Listener).

Working with EventListener in Firebase for Android

Consider, I store the following Key-Value pair in Firebase Database.
Key: "CarBrand" and Value: "Audi"
I read the Value for this Key from Firebase Database and display the Value in TextView. For this, I use an EventListener.
The problem I face with an EventListsner is since it runs on a separate thread, the TextView returns a NullPointerException even before the Value is fetched from Firebase.
To overcome the issue I have been using this dirty trick (using a Handler with 500 to 1000 ms delay). Can someone guide me the right way to fetch and display the Value in TextView or any other Views
PS: Apologies for this codeless Question
Thanks
EDIT: More the Code, better the question. The sample code is as follows
String CardBrand;
private ValueEventListener contentListener() {
contentListener = new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
//Pull Values for all available Keys
CarBrand = dataSnapshot.child("CarBrand").getValue(String.class);
}
#Override
public void onCancelled(DatabaseError databaseError) {}
};
return contentListener;
}
dbContent.addListenerForSingleValueEvent(contentListener());
mTextView.setText(CarBrand);
The above code results in error. To avoid this, I replaced the last line of code with
new Handler().postDelayed(new Runnable() {
#Override
public void run() {
mTextView.setText(CarBrand);
}
}, 1000);
So this is what I have been doing. What would be the right way to display the Text in TextView in my Case?
Your approach is wrong as the listener is updating the String CarBrand But since the code is not in sync and casing the Error. You need to update the code
private ValueEventListener contentListener() {
contentListener = new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
//Pull Values for all available Keys
String CarBrand =
dataSnapshot.child("CarBrand").getValue(String.class);
updateBrand(CarBrand);
}
#Override
public void onCancelled(DatabaseError databaseError) {}
};
return contentListener;
}
dbContent.addListenerForSingleValueEvent(contentListener());
you need to create a Function to update the value
/* updates the Car brand Text
TODO:: You can do anystuff you want to do after anything gets
updated
*/
public void updateBrand(String brand){
mTextView.setText(CarBrand);
}
NOTE : Put Listener on the Data you are actually looking for changes.
If you want to work with the result of some callback (like ValueEventListener) you should execute your code inside the overriding methods.
String cardBrand;
private ValueEventListener contentListener() {
contentListener = new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
//Pull Values for all available Keys
carBrand = dataSnapshot.child("CarBrand").getValue(String.class);
mTextView.setText(carBrand);
}
#Override
public void onCancelled(DatabaseError databaseError) {}
};
return contentListener;
}
dbContent.addListenerForSingleValueEvent(contentListener());
Let me show you small example:
This class responsible for retreiving the link for you, and it doesn't know what will you do with it, so it takes Callback and pass the result to your callback, where you can do anything you want.
public class SomeDataSource {
void getData(Callback callback) {
String url = Backend.GetUrl();
callback.onDownloadUrlReceived(url);
}
}
this is description of callback. Which you can implement to handle data from SomeDataSource
public interface Callback {
void onDownloadUrlReceived(String url)
}
usage
void onCreate(Bundle savedInstanceState) {
SomeDataSource downloader = new SomeDataSource();
//here callback is implemented, and you can handle data from SomeDataSource like you want.
downloader.getData(new Callback() {
#Override
public void onDownloadUrlReceived(String url) {
do wat you want with url
}
});
}

Android Firebase, simply get one child object's data

I have been looking for a way to get one child object's data in Android Firebase.
I have found things like Firebase retrieve child Android. All the solutions are suggesting using a "ChildEventListener", however I need to get this data at this moment, not when it is moved, deleted, updated, etcetera.
My data is kept in https://.firebaseio.com/users//creation as a string. I figure there must be some simple way to access that without needing to do too much, because if I copy the exact URL to my browser, I can see the: 'creation: "2015/05/31 21:33:55"' right there in my "Firebase Forge Dashboard".
How can I access this without a listener?
Firebase listeners fire for both the initial data and any changes.
If you're looking to synchronize the data in a collection, use ChildEventListener. If you're looking to synchronize a single object, use ValueEventListener. Note that in both cases you're not "getting" the data. You're synchronizing it, which means that the callback may be invoked multiple times: for the initial data and whenever the data gets updated.
This is covered in Firebase's quickstart guide for Android. The relevant code and quote:
FirebaseRef.child("message").addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot snapshot) {
System.out.println(snapshot.getValue()); //prints "Do you have data? You'll love Firebase."
}
#Override
public void onCancelled(DatabaseError databaseError) {
}
});
In the example above, the value event will fire once for the initial state of the data, and then again every time the value of that data changes.
Please spend a few moments to go through that quick start. It shouldn't take more than 15 minutes and it will save you from a lot of head scratching and questions. The Firebase Android Guide is probably a good next destination, for this question specifically: https://firebase.google.com/docs/database/android/read-and-write
You don't directly read a value. You can set it with .setValue(), but there is no .getValue() on the reference object.
You have to use a listener. If you just want to read the value once, you use ref.addListenerForSingleValueEvent().
Example:
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) {
}
});
Source: https://www.firebase.com/docs/android/guide/retrieving-data.html#section-reading-once
just fetch specific node data and its working perfect for me
mFirebaseInstance.getReference("yourNodeName").getRef().addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
for (DataSnapshot postSnapshot : dataSnapshot.getChildren()) {
Log.e(TAG, "======="+postSnapshot.child("email").getValue());
Log.e(TAG, "======="+postSnapshot.child("name").getValue());
}
}
#Override
public void onCancelled(DatabaseError error) {
// Failed to read value
Log.e(TAG, "Failed to read app title value.", error.toException());
}
});
I store my data this way:
accountsTable ->
key1 -> account1
key2 -> account2
in order to get object data:
accountsDb = mDatabase.child("accountsTable");
accountsDb.child("some key").addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot snapshot) {
try{
Account account = snapshot.getChildren().iterator().next()
.getValue(Account.class);
} catch (Throwable e) {
MyLogger.error(this, "onCreate eror", e);
}
}
#Override public void onCancelled(DatabaseError error) { }
});

Categories

Resources