How to synchronise methods of Firebase - android

In my Firebase database, I have two children let Child1, Child2. Child1 contains profile information of users as Root/Child1/Uid-User(x)/User_details. Child2 contains transactions done by users as Root/Child2/Uid-User(x)/Transaction_details. I want to retrieve user profile info of one user then transaction of the same user then store it in some object. Then profile info of another user, his transaction and store it in another object and so on.
How can I do that? as Firebase methods run asynchronously so if I create one method to retrieve from Child1 and another for Child2 then they execute asynchronously and create the problem.

You can retrieve it Initially by putting a listener to the userDetail node once this is done ensure retrieved data should not be null after again you make another request to retrive his transction data
Or You Retrieve the both separately and put the result into a 2 HashMap once the data retrieved from both listeners you can merge them based on the key (Ensure that the length of both map should be same)
To ensure Both the listeners are invoked you make like this
Initially put two variables
userProfileHit = 0;
userTransctionHit = 0;
privte void userList () {
userProfileHit ++;
private ValueEventListener userListener = new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
//retriveData
userProfileHit --;
doMergingTask ();
}
#Override
public void onCancelled(DatabaseError databaseError) {
userProfileHit --;
doMergingTask ()
}
};
}
private void transctionList () {
userTransctionHit ++;
private ValueEventListener transctionListener = new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
userTransctioneHit --;
doMergingTask ()
}
#Override
public void onCancelled(DatabaseError databaseError) {
userTransctionHit --;
doMergingTask ()
}
};
}
private void doMergingTask () {
if (userTransctionHit == 0 && userProfileHit == 0) {
// Check Datas are valid and merge it
}
}

My suggestion is that you put the method for retrieving the users transactions inside the onDataChanged method for retrieving profile information. So that, when you get the users profile information, you use it to get users transaction information and create the object. You can nest the firebase event listeners to produce the any outcome you want.
For Example:
final FirebaseDatabase database = FirebaseDatabase.getInstance();
final DatabaseReference ref = database.child("profile");
// Attach the first listener to read the data at our profile reference
ref.addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
final Profile profile = dataSnapshot.getValue(Profile.class);
DatabaseReference transactionRef = database.child("transaction");
// Attach a listener to read the data at our transactions reference
transactionRef.addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
Transaction transaction = dataSnapshot.getValue(Transaction.class);
//
//Do stuff with the Transaction object and Profile object here.
//
}
#Override
public void onCancelled(DatabaseError databaseError) {
System.out.println("The read failed: " + databaseError.getCode());
}
});
}
#Override
public void onCancelled(DatabaseError databaseError) {
System.out.println("The read failed: " + databaseError.getCode());
}
});

Related

Is it possible to sort the Firebase data and apply it to the Real Time DB on Android Studio?

I am trying to sort my Firebase DB data on the Android Studio side and apply it in real-time. My codes successfully updates the view immediately when I make a manual change on my DB, but I wonder if I can sort by name using the code. I thought orderByChild was the function to do that, but it does not change the database.
Function that fetches data from firebase and displays:
private void fetchRepoData(String profileUserID) {
profileUserDatabase = repoReference.child(profileUserID);
//DatabaseReference reposReference = profileUserDatabase.child("Repos");
profileUserDatabase.addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
repoListItems.clear();
repoNameList.clear();
userNameList.clear();
descriptionList.clear();
Toast.makeText(getActivity(), "changed", Toast.LENGTH_SHORT).show();
for(DataSnapshot snapshot : dataSnapshot.getChildren()) {
repoNameList.add(snapshot.getValue(RepoInfo.class).getRepoName());
userNameList.add(snapshot.getValue(RepoInfo.class).getOwner());
descriptionList.add(snapshot.getValue(RepoInfo.class).getDescription());
}
for(int i = 0; i < repoNameList.size(); i++) {
repoListItems.add(new RepositoryItem(i, repoNameList.get(i), userNameList.get(i), descriptionList.get(i)));
}
adapter.notifyDataSetChanged();
}
#Override
public void onCancelled(DatabaseError databaseError) {
}
});
}
Radio button for sorting by name
sortByNameRadio.setOnClickListener(
new View.OnClickListener() {
#Override
public void onClick(View v) {
repoReference.child("subchild").orderByChild("name");
adapter.notifyDataSetChanged();
}
}
);
When you call orderByChild() it returns a new Query object. You'll need to store a reference and attach a listener that query object.
profileUserDatabase = repoReference.child(profileUserID);
Query query = repoReference.child("subchild").orderByChild("name");
query.addValueEventListener(new ValueEventListener() {
for(DataSnapshot snapshot: dataSnapshot.getChildren()) {
System.out.println(snapshot.getKey());
...

Firebase value event listener not working

In my app, I have an id that is supposed to be pulled down from firebase real time database. If I pull it down and it sees there is not an id available, then it sets the id to 1. I did a debug, and the id is set to 1, but after the listener finishes, the id goes to 0. I don't know why or what is causing this, but here is my code.
Code for listener:
userRef.child("id").
addSingleValueEventListener(new ValueEventListener() {
#Override public void onDataChange (DataSnapshot dataSnapshot){
try {
String value = dataSnapshot.getValue().toString();
id = Integer.parseInt(value);
} catch (NullPointerException e) {
id = 1; //After this, id is 1
e.printStackTrace();
}
}
#Override public void onCancelled (DatabaseError databaseError){
id = 1;
}
}); //Now id is 0
Two things come to mind from this question, one about Firebase listeners and the second about removing excess code.
A fundamental thing about Firebase listeners is that they are asynchronous, meaning your code does not wait for the result before executing the next line. So look at the comments in this skeleton code:
userRef.child("id").
addSingleValueEventListener(new ValueEventListener() {
#Override public void onDataChange (DataSnapshot dataSnapshot){
// code here does not get executed straight away,
// it gets executed whenever data is received back from the remote database
}
#Override public void onCancelled (DatabaseError databaseError){
}
});
// if you have a line of code here immediately after adding the listener
// the onDataChange code *won't have run*. You go straight to these lines
// of code, the onDataChange method will run whenever it's ready.
So this means that if you want to do something with the data you are getting in onDataChange you should put that code inside the onDataChange method (or some other method called from there or in some other way running that code after the data has been delivered back).
Regarding the second part, a slightly more Firebasey way of checking for existence of an int and getting the value would be:
#Override public void onDataChange (DataSnapshot dataSnapshot){
if (dataSnapshot.exists()) {
id = dataSnapshot.getValue(Integer.class);
} else {
id = 1;
}
}
you need to set id = 1 into firebase database using setValue(), try below line of code it might help you
userRef.child("id").
addSingleValueEventListener(new ValueEventListener() {
#Override public void onDataChange (DataSnapshot dataSnapshot){
try {
String value = dataSnapshot.getValue().toString();
id = Integer.parseInt(value);
} catch (NullPointerException e) {
// if id=0 then set id=1 using setvalue()
// this will set value of Id =1 in firebase database
id=1;
userRef.child("id").setValue(id);
log.d(LOG_TAG, e.printStackTrace());
}
}
#Override public void onCancelled (DatabaseError databaseError){
id=1;
userRef.child("id").setValue(id);
}
});
I had kinda the same problem of getting an object out of FireBase Realtime Database,
and as #Lewis McGeary wrote, I Just made my program Wait until all the data gets retrived from the Database
Here's my sample code:
BarReference = FirebaseDatabase.getInstance().getReference("Users").child(myUID);
ValueEventListener UserChangeListener = new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
me = dataSnapshot.getValue(UserDataSet.class);
}
#Override
public void onCancelled(DatabaseError databaseError) {
Log.w(TAG, "loadPost:onCancelled", databaseError.toException());
}
};
BarReference.addValueEventListener(UserChangeListener);
while (me == null){
Log.w(TAG, "Waiting For The Data to Download");
progresBar.setVisibility(View.VISIBLE);
return;
}
Hope that Helps! (^_^)
Using "addValueEventListener" instead of "addListenerForSingleValueEvent" worked for me.
ValueEventListener locListener = new ValueEventListener() {
#Override
public void onDataChange(#NonNull #NotNull DataSnapshot snapshot) {
//do stuff here
}
#Override
public void onCancelled(#NonNull #NotNull DatabaseError error) {
Log.d(TAG, error.toString());
}
};
db.addValueEventListener(locListener);

Firebase query n items and listen to new only

I am trying to query last 5 items and then listen only to new childs. However,
when I add new child, childeventlistener returns the last child from thos 5 i queried earlier.
query = myRef.orderByKey().limitToLast(5);
query.addListenerForSingleValueEvent(new ValueEventListener() {
#Override public void onDataChange(DataSnapshot dataSnapshot) {
if (dataSnapshot.exists()) {
// dataSnapshot is the "issue" node with all children with id 0
int oldSize = mData.size();
List<ChatMessageEntity> data = new ArrayList<>();
for (DataSnapshot issue : dataSnapshot.getChildren()) {
// do something with the individual "issues"
ChatMessageEntity chat = issue.getValue(ChatMessageEntity.class);
data.add(0,chat);
}
mData.addAll(data);
adapter.notifyItemRangeInserted(oldSize, mData.size());
}
query.addChildEventListener(newMessageListener);
}
#Override public void onCancelled(DatabaseError databaseError) {
}
});
With this type of scenario you're better off using a ChildEventListener. This has a method onChildAdded that will be called for (up to) five children initially and then for each new child added later.
query = myRef.orderByKey().limitToLast(5);
query.addChildEventListener(new ChildEventListener() {
#Override
public void onChildAdded(DataSnapshot dataSnapshot, String previousChildName) {
ChatMessageEntity chat = issue.getValue(ChatMessageEntity.class);
data.add(0,chat);
adapter.notifyItemInserted(mData.size()-1);
}
...
#Override public void onCancelled(DatabaseError databaseError) {
throw databaseError.toException(); // don't ignore these
}
});
The above code works, but is missing handling of previousChildName (as children that arrive later might not be added to the end of the list), onChildChanged, onChildMoved and onChildRemoved. Adding handling for all of these is a fun exercise, but quite a bit of work and a bit tricky to get right. If you want to complete the work, I recommend taking a look at the RecyclerViewAdapter in FirebaseUI and its work horse FirebaseArray - which are nicely battle tested.
For more information see the Firebase documentation on listening for child event.
The best i make up is to load required number of items, save key of the newst one. Remove item with that key from list. Create new query which starts from key of the newest and add childEventListener to it.
query = myRef.orderByKey().limitToLast(20);
query.addListenerForSingleValueEvent(new ValueEventListener() {
#Override public void onDataChange(DataSnapshot dataSnapshot) {
if (dataSnapshot.exists()) {
// dataSnapshot is the "issue" node with all children with id 0
int oldSize = mData.size();
//List<ChatMessageEntity> data = new ArrayList<>();
for (DataSnapshot issue : dataSnapshot.getChildren()) {
// do something with the individual "issues"
ChatMessageEntity chat = issue.getValue(ChatMessageEntity.class);
chat.key = issue.getKey();
mData.add(chat);
//data.add(0, chat);
}
//mData.addAll(data);
}
Log.e("mcheck", "onDataChange: " + mData.get(0).message);
Query query;
if (mData.size() > 0) {
query = myRef.orderByKey().startAt(mData.get(mData.size() - 1).key);
olderstKey = mData.get(0).key;
mData.remove(mData.size() - 1);
adapter.notifyItemRemoved(mData.size()-1);
adapter.notifyItemRangeInserted(0, mData.size());
//adapter.notifyDataSetChanged();
query.addChildEventListener(newMessageListener);
} else {
myRef.addChildEventListener(newMessageListener);
}
}
#Override public void onCancelled(DatabaseError databaseError) {
}
});

How can I retrieve some fields from my Firebase database before I push any changes to it?

Currently I create a Listing object and store a bunch of fields in there. Two of the fields I need to store are the current User's email and name. I am trying to get those two fields as follows
dbRef = database.getReference().child("Users").child(emailKey);
dbRef.addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
UserInfo userIn = dataSnapshot.getValue(UserInfo.class);
email1 = userIn.email;
sellerName = userIn.username;
}
#Override
public void onCancelled(DatabaseError databaseError) {
}
});
After this bit of code I have the line
DatabaseReference temp = dbRefL.push();
temp.setValue(l);
All of this code is called by me pressing a button. The first time I press the button, the entire Listing object is pushed to Firebase just the way I want it EXCEPT the user email and username aren't there because they're blank. The second time I press the button the Strings are there how I want.
My guess is that this is because OnDataChange only executes after I push the Listing object. Is this true? How can I get OnDataChange to execute before I push the listing object?
The listener onDataChange() callbacks are asynchronous. The reason that your email and username is blank is because onDataChange hasn't been executed yet to make sure you push data after email and username are retrieve, put the code inside onDataChange()
dbRef = database.getReference().child("Users").child(emailKey);
dbRef.addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
UserInfo userIn = dataSnapshot.getValue(UserInfo.class);
email1 = userIn.email;
sellerName = userIn.username;
//set up your l value
DatabaseReference temp = dbRefL.push();
temp.setValue(l);
}
#Override
public void onCancelled(DatabaseError databaseError) {
}
});
Once you press the button above code should be called to obtain email and username then push the data as you want.
User addValueEvenListener like :
DatabaseReference map = database.getReference("field_name");
map.addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
String data = (String) dataSnapshot.getValue();
}
#Override
public void onCancelled(DatabaseError databaseError) {
}
});
****OR using****
database.getReference("field_name").addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
Log.e(TAG, "Field data", dataSnapshot.getValue(String.class));
}
#Override
public void onCancelled(DatabaseError databaseError) {
// Failed to read value
Log.e(TAG, "Failed to read user", databaseError.toException());
}
});

Android - Firebase - Issue with getChildrenCount() method

Currently, I'm building an Android App which is using a Firebase Database.
In my code, I'm trying to get the number of Children in a specific path of my Firebase Database. Having defined the reference of the Firebase specific path, I have made a Listener in order to get the DataSnapshot and then to call getChildrenCount().
After that, I want to use this result in a FOR-Loop. However, the code doesn't work.
It appears that it is executed first the FOR-Loop (I realised that because Log.v("NUM_OF_PRODUCTS",numOfProducts+"") outputs 0 insteed of 3) and the Listener is executed after, while in the code, listener code comes first and FOR-Loop code next.
Part of my code:
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_product_selection);
selected_cat = getIntent().getExtras().getString("pass_selected_cat");
listView = (ListView) findViewById(R.id.listView);
final ArrayAdapter<String> adapter = new ArrayAdapter<>(this, android.R.layout.simple_list_item_1, arrayList);
listView.setAdapter(adapter);
fbdatabase = FirebaseDatabase.getInstance();
fbref_products = fbdatabase.getReference("PRODUCTS/SM_0/" + selected_cat);
//Listener for getting numOfProducts.
fbref_products.addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
numOfProducts = (int) dataSnapshot.getChildrenCount();
}
#Override
public void onCancelled(DatabaseError databaseError) {
}
});
//Print numOfProducts before FOR-Loop being executed.
Log.v("NUM_OF_PRODUCTS",numOfProducts+"");
//FOR-Loop
for (int i = 0; i < numOfProducts; i++) {
String str = i + "";
DatabaseReference product_ref = fbref_products.child(str);
product_ref.addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
Map<String, Object> map = (Map<String, Object>) dataSnapshot.getValue();
String productName = (String) map.get("productName");
arrayList.add(productName);
adapter.notifyDataSetChanged();
}
#Override
public void onCancelled(DatabaseError databaseError) {
//textView.setText("The read failed: " + databaseError.getMessage());
}
});
}
My code works fine if instead the variable numOfProducts, we had a specific number.
Could anyone help me??
Data from the Firebase Database is loaded asynchronously. By the time you run your for loop it hasn't been loaded yet.
It is easiest to see this if you place a few log statements in your code:
System.out.println("Before attaching listener");
fbref_products.addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
System.out.println("In onDataChange");
}
#Override
public void onCancelled(DatabaseError databaseError) {
}
});
System.out.println("After attaching listener");
When you run this, the output will be:
Before attaching listener
After attaching listener
In onDataChange
This is probably not the order that you expected. But it explains perfectly why numOfProducts is still 0 when you start the loop: the data simply hasn't been loaded from Firebase yet.
In initial reaction from pretty much every developer is "I don't want this. How do I make it execute in the right order?" This is a natural response, but one that you'll have to suppress to get anywhere when programming against web/cloud APIs. I also recommend reading my answer here: Setting Singleton property value in Firebase Listener
The solution is to reframe your solution from "first I'll get the number of products and then I'll loop over the products" to "whenever I get the products, I'll loop over them".
In code that is:
fbref_products.addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
numOfProducts = (int) dataSnapshot.getChildrenCount();
for (int i = 0; i < numOfProducts; i++) {
String str = i + "";
DatabaseReference product_ref = fbref_products.child(str);
product_ref.addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
Map<String, Object> map = (Map<String, Object>) dataSnapshot.getValue();
String productName = (String) map.get("productName");
arrayList.add(productName);
adapter.notifyDataSetChanged();
}
#Override
public void onCancelled(DatabaseError databaseError) {
//textView.setText("The read failed: " + databaseError.getMessage());
}
});
}
}
#Override
public void onCancelled(DatabaseError databaseError) {
}
});
Aside from the whole dealing with asynchronous events, you can significantly simplify this code:
fbref_products.addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
for (DataSnapshot productSnapshot: dataSnapshot.getChildren()) {
String productName = productSnapshot.child("productName").getValue(String.class);
arrayList.add(productName);
adapter.notifyDataSetChanged();
}
}
#Override
public void onCancelled(DatabaseError databaseError) {
}
});
Changes:
this uses only a single listener. Your second listener is not needed, since retrieving a node already retrieves all data under that node as well. You can loop over the child nodes.
this doesn't first extract a HashMap, but simply reads the data from the child snapshot directly.
One more change you should consider is to keep a separate list of product names. You now retrieve all product data to show a list of names, which is wasteful. If you keep a separate list of just the product names, you can load that instead. You'll find that a common theme when using Firebase (or almost any NoSQL database): model the data in your database the way you show it on the screen.

Categories

Resources