In our database structure, each "User" holds multiple bid-Ids, each bid-Id represents an entry point in the "Jobs" database.
Currently when we want to iterate through all jobs of a single person the code looks something similar to:
final DatabaseReference mDatabaseJobs = FirebaseDatabase.getInstance().getReference().child("Jobs");
DatabaseReference mDatabaseUser = FirebaseDatabase.getInstance().getReference().child("Users").child(currentUser.getUid());
mDatabaseUser.addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
DataSnapshot userBids = dataSnapshot.child("listOfBids");
final int num_user_bids = (int) userBids.getChildrenCount();
for(final DataSnapshot job : userBids.getChildren()){
String jobId = job.getValue(String.class);
mDatabaseJobs.child(jobId).addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
bid_count.addAndGet(1);
Here is the database structure, the objective is somehow to get all the jobs of a specific person.
Is there a way to retrieve all the jobs at once? mabye a list of keys? and not calling the add-listener for each one?
If not, I know that firebase fetches the data asynchronously, but does it do it in parallel?
i.e we currently increment an atomic integer to know whether firebase has already fetched all the jobs, because we don't want to threads to increment the counter at the same time, is it necessary in our current implementation?
Thanks!
You can keep a HashMap outside the "Users", populate it with current user's list of jobs. Then attach another listener to "Jobs" and iterate over your hashMap and grab the specific jobs you want. Below is a reference code:
HashMap<Integer, String> listOfJobs = new HashMap<>();
DatabaseReference mDatabaseUser = FirebaseDatabase.getInstance().getReference().child("Users").child(currentUser.getUid()).child("listOfBids");
mDatabaseUser.addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
if (dataSnapshot.exists) {
listOfJobs.putAll((Map) dataSnapshot.getValue());
}
// Here you can add a listener to the entire "Jobs" reference like this
DatabaseReference mDatabaseJobs = FirebaseDatabase.getInstance().getReference().child("Jobs");
mDatabaseJobs.addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot userDataSnapshot) {
if (userDataSnapshot.exists) {
for (String jobId : listOfJobs.values()) {
if (userDataSnapshot.hasChild(jobId) {
DataSnapshot job = userSnapshot.child(jobId);
// here you can use job snapshot
bid_count.addAndGet(1); // something like this?
}
}
}
}
});
Basically, you are adding a listener to something that is not nested or deep and using the loop to get the children, instead of adding a listener to nested children.
Hope this helps.
Related
I have the following data structure in my firebase. I am implementing a coupon based system where you enter a code in dialogue box and it is searched across the database. So I have been trying to figure out this but I haven't found the perfect query that can search the code, if found, then get all the other child values too.
Below is the code that i have been trying with:
private void couponsearch() {
final EditText taskEditText = new EditText(this);
AlertDialog.Builder dialog = new AlertDialog.Builder(this)
.setTitle("Akiba Yangu")
.setMessage("Enter Akiba Code Here.")
.setIcon(R.drawable.akyi)
.setView(taskEditText).setPositiveButton("SAVE",
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog,int id) {
String stringe = taskEditText.getText().toString();
DatabaseReference mDatabase = FirebaseDatabase.getInstance().getReference();
mDatabase.child("codes").orderByChild("code").equalTo(stringe).addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
Coupons coo = dataSnapshot.getValue(Coupons.class);
String name = dataSnapshot.getKey();
int codez = coo.getValuee();
code.setText(name);
if(dataSnapshot != null && dataSnapshot.getChildren().iterator().hasNext()){
for(DataSnapshot ds : dataSnapshot.getChildren()) {
codegotten();
}
}else {
nocode();
}
int val = coo.getValuee();
Akibasavings as = new Akibasavings();
String uid = FirebaseAuth.getInstance().getCurrentUser().getUid();
System.out.println(val);
code.setText(name);
as.setName(name);
as.setAmount(val);
final Firebase ref = new Firebase("https://akiba-c9600.firebaseio.com/");
Firebase newRef = ref.child("Savings"+uid).push();
newRef.setValue(as);
sendNotification();
}
#Override
public void onCancelled(DatabaseError databaseError) {
}
public void onCancelled(FirebaseError firebaseError) {
//Username Does Not Exist
}
});
}
});
dialog.show();
}
I would like to search the code, check if it is really there or not: if it is there I need to get all the other values too, i.e there are three nodes in every child. code, brand, value. After I have checked that the code exists, I would like to also get the other values associated with it. Regards
To achieve this, i recomand you change the structure of your database a little bit by adding a new node named coupons. Your database should look like this:
Firebase-root
--- coupons
TTUUPP: true
KKLLOO: true
To check if a coupon exists in your database, just add a listener on the new created node and use exists() method. This is a coomon practice within Firebase named denormalization and is for simplify and reduce query and bandwith. This what you need.
What you need is Query.
DatabaseReference mDatabase = FirebaseDatabase.getInstance().getReference();
Query mQuery = mDatabase.child("codes").orderByChild("code").equalTo(stringe);
mQuery.addListenerForSingleValueEvent(new ValueEventListener() {
// use single value event listener to detach listener immediately after query
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
if (dataSnapshot.getValue() != null) {
// code exists, cast your data to relevant object
// note that if multiple entries exists, you need to loop through them
} else {
// code does not exists
}
}
});
Read the official document here.
Note that you must remember to index your firebase properly! Query on firebase is not the same as local database query. If you do not index your firebase, the query will download everything under the codes section into user phone, before doing the search locally on user phone. This will consume high bandwidth and memory. Learn more about index here. Read about my personal issue with index previously here.
When you execute a query against the Firebase Database, there will potentially be multiple results. So the snapshot contains a list of those results. Even if there is only a single result, the snapshot will contain a list of one result.
Your onDataChange() needs to handle the fact that the snapshot contains a list of result by looping over DataSnapshot.getChildren():
mDatabase.child("codes").orderByChild("code").equalTo(stringe).addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
for (DataSnapshot childSnapshot: dataSnapshot.getChildren()) {
Coupons coo = childSnapshot.getValue(Coupons.class);
String name = childSnapshot.getKey();
int codez = coo.getValuee();
code.setText(name);
...
}
}
my data look like this
and I simply want to add an object at index 3. How could I add it there. Is there any way to add an object without iteration or I have to iterate and getChildCount and then append new child("3") and it's data to it.
TransGenderBO transGenderBO = new TransGenderBO();
transGenderBO.setName("pushName");
transGenderBO.setAge(13);
mRef.child("").setValue(transGenderBO);
there is no method in mRef for getting child count and appending new item at 3 position..
Edit after using Frank code but still not working
Query last = mRef.orderByKey().limitToLast(1);
last.addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
int lastIndex = 0;
for (DataSnapshot childSnapshot: dataSnapshot.getChildren()) {
lastIndex = Integer.parseInt(childSnapshot.getKey());
}
TransGenderBO transGenderBO = new TransGenderBO();
transGenderBO.setName("pushName");
transGenderBO.setAge(13);
mRef.child(""+(lastIndex+1)).setValue(transGenderBO);
}
#Override
public void onCancelled(DatabaseError databaseError) {
Toast.makeText(mContext,databaseError.getMessage(),Toast.LENGTH_LONG).show();
}
});
There is a good reason that the Firebase documentation and blog recommend against using arrays in the database: they don't work very well for multi-user applications where users can be offline.
To add the next element to your array here, you'll have to download at the very least the last element of the array to know the index of the next element:
Query last = root.orderByKey().limitToLast(1);
last.addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
int lastIndex;
for (DataSnapshot childSnapshot: dataSnapshot.getChildren()) {
lastIndex = Integer.parseInt(childSnapshot.getKey());
}
root.child(""+(lastIndex+1)).setValue(true);
}
But this has an inherent race-condition. When multiple users are adding elements to the array at the same time, they may end up writing to the same index.
To prevent this you can use a Firebase transaction. With this you get the current value from a location and in exchange return the new value you want at that location. This ensures that no data is overwritten between users, but means that you have to download the entire array.
And neither of these scenarios works when a user is not connected to the network.
Firebase instead recommends using so-called push IDs, which:
Generate a always-increasing key that is guaranteed to be unique.
Do not require reading any data - they are generated client-side and are statistically guaranteed to be unique.
Also work when a user is offline.
The only disadvantage is that they're not as easily readable as array indexes.
Get your data like this
private ArrayList<TransGenderBO> transGenderBO;
FirebaseDatabase.getInstance().getReference().child("Main")
.addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
transGenderBO = (ArrayList<TransGenderBO>) dataSnapshot.getValue();
}
#Override
public void onCancelled(DatabaseError databaseError) {
}
});
and set your value like this
TransGenderBO transGender = new TransGenderBO();
transGender.setName("pushName");
transGender.setAge(13);
FirebaseDatabase.getInstance().getReference().child("Main").child(String.valueOf(transGenderBO.size())).setValue(transGender);
or U can set this way too
TransGenderBO transGender = new TransGenderBO();
transGender.setName("pushName");
transGender.setAge(13);
TransGenderBO.add(transGender);
FirebaseDatabase.getInstance().getReference().child("Main")
.setValue(transGenderBO);
I have successfully implemented geoFire to get the keys of entries from a main firebase node within the user's area.
I have then attempted to store those keys in a new, unique node, in order to use them later to populate a viewHolder, as this seems to be best practice.
The problem is that I don't want all of the objects stored in the unique node to actually populate the viewHolder; I only want a subset of them.
If I filter inside the populateViewHolder method, e.g.,
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
SwarmReport retrievedModel = dataSnapshot.getValue(SwarmReport.class);
if(!retrievedModel.isClaimed()){
viewHolder.bindSwarmReport(retrievedModel);
}
}
I get some items in the viewHolder that are blank.
If, instead, I try to filter before setting up the FirebaseAdapter, e.g. in my addGeoQueryEventListener methods, I have to add an addListenerForSingleValueEvent to check the criteria by which I aim to filter:
public void onKeyEntered(String key, GeoLocation location) {
claimCheckKey = key;
final DatabaseReference claimCheckRef = FirebaseDatabase.getInstance().getReference("all").child(key);
claimCheckRef.addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
SwarmReport claimCheckSwarmReport = dataSnapshot.getValue(SwarmReport.class);
if (!claimCheckSwarmReport.isClaimed()){
swarmReportIds.add(claimCheckKey);
}
}
It seems that onGeoQueryReady executes before addListenerForSingleValueEvent in onKeyEntered is finished. Any recommendations on how to get onGeoQueryReady to wait for all of the swarmReportIds.add(claimCheckKey)s to complete?
In case it's helpful, here's a snapshot of my firebase structure:
I don't really understand what you are trying to achieve but I will take a guess and say that you want not only the closest key but you also want to filter the closest key based on some other criteria, e.g., closest restaurant then prices,
The workaround I did to achieve this, (still looking for better options) is to set a flag inside the onDatachange method that I called on inside the onKeyentered method, that compares entries inside that node, (for sake of argument we will say prices)
UPDATE
#Override
public void onKeyEntered(final String key, GeoLocation location) {
final DatabaseReference db = FirebaseDatabase.getInstance().getReference("<your ref>"+key);
db.addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
for (DataSnapshot ds : dataSnapshot.getChildren()) {
String dishPrice=ds.getValue(String.class);
int price=Integer.parseInt(dishPrice);
if (price<=12) {
isMatch=true;
}
}
if(isMatch){
getRestaurants(key);
}
isMatch=false;
}
And outside the for each loop of onDataChange I set to execute the function that populates the arraylist of my adapter only when the function is true
getRestaurants method
private void getRestaurants(final String id) {
final DatabaseReference temp=FirebaseDatabase.getInstance().getRef("<your
ref>"+id)
temp.child(id).addListenerForSingleValueEvent(new
ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
Restaurant restaurant = dataSnapshot.getValue(Restaurant.class);
restaurant.setName(restaurant.getName());
restaurant.setImageUri(restaurant.getImageUri());
restaurant.setUid(id);
//add elemens to arraylist
resList.add(restaurant);
adapter.notifyDataSetChanged();
}
Another suggestion would be to make a function on the cloud, (assuming again) in your case that all you need is to monitor whether the value of "claimed" changes, so you can set an onUpdateevent trigger or and onWrite and check the value in onChanged() and get the current value (true/false)after that you can make use of geoFire queries and get the close by users and update their nodes depending on the value.
Event Triggers
Cloud Functions
I'm trying to display the "loc" of a part if its part number I given.
Here is what the data structure looks like:
{
"parts":{
"14521845": { "name":"TOOL EC160B/EC180B/EC210B/EC240", "loc":"EXC1", "sloc":"B3EGU01C03"},
"12829050": { "name":"SWITCH; IGNITION SWITCH", "loc":"PS01", "sloc":"85-06-013"},
"12829050": { "name":"SWITCH; IGNITION SWITCH", "loc":"COM1", "sloc":"B3RGK03D06"},
"20044893": { "name":"PARTS CATALOG_ENG_SPA_FRE_GER_KOR_EC210D", "loc":"EXC1", "sloc":"B3EGT01B02"}
}
}
Activity Code:
FirebaseDatabase firebaseDatabase=FirebaseDatabase.getInstance();
DatabaseReference databaseReference =firebaseDatabase.getReference("parts/"+curP);
databaseReference.addChildEventListener(new ChildEventListener() {
#Override
public void onChildAdded(DataSnapshot dataSnapshot, String s) {
Products data=dataSnapshot.getValue(Products.class);
Log.i("",String.valueOf(data.getLoc()));
}
getLoc is the getter function for the Product class, and it returns the corresponding "loc" for the given curP. curP denoted the child values in parts.
The logic seems right to me, but I am not getting an output. Where am I going wrong here?
try this
getReference("parts").child(curP).addChildEventListener(new ChildEventListener() {
#Override
public void onChildAdded(DataSnapshot dataSnapshot, String s) {
Products data = dataSnapshot.getValue(Products.class);
Log.i("", String.valueOf(data.getLoc()));
}
});
The problem is that what you are getting in onChildAdded() is not a whole Product object as you expect it to be.
In your database reference you are targeting a specific Product ("parts/"+curP) but using a ChildEventListener. The children of a specific product node are name, loc and sloc, so the onChildAdded() will trigger several times, giving you each of these properties as a dataSnapshot separately.
The two patterns you might use to get whole Product objects are either:
add a ChildEventListener directly to the "parts" node and you will get each of the Products as a child of that node, or;
if you are adding a listener directly to the node of a particular product, use a ValueEventListener, to get the whole of that nodes entry as one dataSnapshot.
You can try to use ValueEventListener. If you want read data once so use the addListenerForSingleValueEvent method, something like this:
private void getFirebaseLocValue(int curP) {
FirebaseDatabase firebase = FirebaseDatabase.getInstance();
DatabaseReference mDatabase = firebase.getReference("parts");
mDatabase.child(Integer.toString(curP))
.addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
if(dataSnapshot.hasChildren()) {
Products data = dataSnapshot.getValue(Products.class);
Log.e("TAG", data.getLoc());
}
}
#Override
public void onCancelled(DatabaseError databaseError) {
}
});
}
Or you can use addValueEventListener and will get data with any changes. I really don't think that ChildEventListener is a good idea to retrieve data from Firebase.
I have this firebase database
and i need to get all phone numbers of users , which listener shall i use to get all childes?
Every user is added as an object with user-ID as a name of that object, I need to retrieve this objects without knowing that user-ID
I searched the documentation , it's related to DataSnapshot but i couldn't get a DataSnapshot without a listener ! is it correct to seek a DataSnapshout or shall i use something else
First retrieve your users datasnapshot.
//Get datasnapshot at your "users" root node
DatabaseReference ref = FirebaseDatabase.getInstance().getReference().child("users");
ref.addListenerForSingleValueEvent(
new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
//Get map of users in datasnapshot
collectPhoneNumbers((Map<String,Object>) dataSnapshot.getValue());
}
#Override
public void onCancelled(DatabaseError databaseError) {
//handle databaseError
}
});
Then loop through users, accessing their map and collecting the phone field.
private void collectPhoneNumbers(Map<String,Object> users) {
ArrayList<Long> phoneNumbers = new ArrayList<>();
//iterate through each user, ignoring their UID
for (Map.Entry<String, Object> entry : users.entrySet()){
//Get user map
Map singleUser = (Map) entry.getValue();
//Get phone field and append to list
phoneNumbers.add((Long) singleUser.get("phone"));
}
System.out.println(phoneNumbers.toString());
}
This listener will only retrieve the datasnapshot when explicitly called. Consider storing a list of numbers under an "allPhoneNumbers" node in addition to your users node. This will make your datasnapshots lighter and easier to process when collecting all numbers. If you have say hundreds of users, the "users" datasnapshot will be way too big and the "allPhoneNumbers" list will be far more efficient.
The above code was tested on your sample database and does work. However, your phone field may need to be casted to String or int depending on your user's phone instance field's type.
DatabaseReference ref1= FirebaseDatabase.getInstance().getReference();
DatabaseReference ref2,ref3,ref4;
ref2 = ref1.child("User");
ref2.addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
Userlist = new ArrayList<String>();
// Result will be holded Here
for (DataSnapshot dsp : dataSnapshot.getChildren()) {
Userlist.add(String.valueOf(dsp.geValue())); //add result into array list
}
/* userlist will store all values of users, then point to every userlist item
and get mobile numbers*/