I'm developing an application for Android that uses firebase.
The application has Users and each user has Friends.
users: {
one: {
name: Mark,
friends: {
two: true,
three: true
},
two: {
name: Carl
},
three: {
name: Gustav
}
}
In this example, Mark has two friends (Carl and Gustav), the other two don't have friends.
I want to get a Mark's Friends List.
String userId = "one";
DatabaseReference friendsDb = db.getReference("users").child(userId).child("friends");
final DatabaseReference usersDb = db.getReference("users");
ValueEventListener friendsListener = new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
GenericTypeIndicator<LinkedHashMap<String,Boolean>> t = new GenericTypeIndicator<LinkedHashMap<String,Boolean>>() {};
LinkedHashMap<String,Boolean> tDataset = dataSnapshot.getValue(t);
users.clear();
for( String userId : tDataset.keySet() ) {
usersDb.child(userId).addListenerForSingleValueEvent(
new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
User user = dataSnapshot.getValue(User.class);
// how to return the user???
// users.add(user);
}
});
}
mAdapter.notifyDataSetChanged();
}
};
friendsDb.addValueEventListener(friendsListener);
Am I using a correct approach about data modeling and indexes?
How is it supposed to give me back the user list that I need?
I understand that listening to a resource it is an async operation, is there a way to get the values that I need in one shot?
Any help will be appreciated! Thanks!
EDIT
Solution proposed by Frank van Puffelen it's correct in the concept but it's not correctly implemented. The concept is to call the mAdapter.notifyDataSetChanged(); when all the children has been retrieved from firebase db. But it have to check the dimension of the first snapshot, intead of the second, as below.
DatabaseReference friendsDb = db.getReference("users").child(userId).child("friends");
final DatabaseReference usersDb = db.getReference("users");
ValueEventListener friendsListener = new ValueEventListener() {
#Override
public void onDataChange(final DataSnapshot dataSnapshot) {
GenericTypeIndicator<HashMap<String,Boolean>> t = new GenericTypeIndicator<HashMap<String,Boolean>>() {};
HashMap<String,Boolean> tDataset = dataSnapshot.getValue(t);
final int usersSize = tDataset.size();
users.clear();
for( String userId : tDataset.keySet() ) {
usersDb.child(userId).addListenerForSingleValueEvent(
new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
User user = dataSnapshot.getValue(User.class);
users.add(user);
if (users.size() == usersSize) {
mAdapter.notifyDataSetChanged();
}
}
});
}
}
};
friendsDb.orderByKey().addValueEventListener(friendsListener);
There is no Firebase equivalent of SQLs WHERE id IN (1,2,3). Performance-wise that is not needed, since Firebase pipelines the requests.
You code looks fine to me, except that you're not adding the user to the list. I expect that you're having trouble defining the "exit condition" for that loop, which is:
ValueEventListener friendsListener = new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
GenericTypeIndicator<LinkedHashMap<String,Boolean>> t = new GenericTypeIndicator<LinkedHashMap<String,Boolean>>() {};
LinkedHashMap<String,Boolean> tDataset = dataSnapshot.getValue(t);
users.clear();
for( String userId : tDataset.keySet() ) {
usersDb.child(userId).addListenerForSingleValueEvent(
new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
User user = dataSnapshot.getValue(User.class);
users.add(user);
if (users.size() == dataSnapshot.getChildrenCount()) {
mAdapter.notifyDataSetChanged();
}
}
});
}
}
};
Try arranging your data this way:
users: {
one: {
name: Mark
},
two: {
name: Carl
},
three: {
name: Gustav
}
},
friends : {
Mark : {
two : true,
three : true
}
}
Related
i have a real time firebase database ...
1 - i need to create a child when there is no child exist in database in the name "book"
book structure:
"book":[{slno:1,
"name":"harry potter",
"author":"J. K. Rowling"
}]
2- if database have the child named "book" then i need to create new child for "book"
but this time i want the "slno" , last added child's "slno + 1"
{ slno:"//last added child's slno + 1",
"name":"new book",
"author":"sample author"
}
tell me a best way to write a code on above situation ...
i am new to firebase...
[platform : Android]
i already tried this[and it's working]
but i don't know its good solution when happen database write operation
from multiple divices at same time [i want , never repeat slno]
... any one have better solution
DatabaseReference rootRef = FirebaseDatabase.getInstance().getReference();
rootRef.addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot snapshot) {
if (snapshot.hasChild("book")) {
// run some code
secondtime();
}else {
final DatabaseReference databaseP2 = FirebaseDatabase.getInstance().getReference();
BookModelClass bookModel = new BookModelClass(1,"harry potter","J. K. Rowling");
databaseP2.child("book").push().setValue(bookModel);
}
}
#Override
public void onCancelled(#NonNull DatabaseError databaseError) {
}
});
private void secondtime(){
final DatabaseReference databaseP2 = FirebaseDatabase.getInstance().getReference();
Query lastQuery = databaseP2.child("book").orderByKey().limitToLast(1);
lastQuery.addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
int slno = 0 ;
for (DataSnapshot postSnapshot : dataSnapshot.getChildren()) {
slno =postSnapshot.child("slno").getValue(Integer.class);
}
final DatabaseReference databaseP2 = FirebaseDatabase.getInstance().getReference("book");
BookModelClass bookModel = new BookModelClass(slno+1,"new book","sample author");
databaseP2.push().setValue(bookModel);
}
#Override
public void onCancelled(DatabaseError databaseError) {
//Handle possible errors.
}
});
}
Since the new value of the node depends on the existing value (or its existence at all), you'll want to use a Firebase Database transaction for this operation.
This means you'll need to read the entire book node, but your current solution also does that, so it's the same in read performance. It'll be something along these lines:
rootRef.runTransaction(new Transaction.Handler() {
#Override
public Transaction.Result doTransaction(MutableData mutableData) {
if (mutableData.hasChildren()) {
int slno = 0 ;
for (MutableData postData: mutableData.getChildren()) {
slno = postData.child("slno").getValue(Integer.class);
}
BookModelClass bookModel = new BookModelClass(slno+1,"new book","sample author");
String newKey = rootRef.push().getKey();
// Set value and report transaction success
mutableData.child(newKey).setValue(bookModel);
}
return Transaction.success(mutableData);
}
#Override
public void onComplete(DatabaseError databaseError, boolean b,
DataSnapshot dataSnapshot) {
// Transaction completed
Log.d(TAG, "bookTransaction:onComplete:" + databaseError);
}
});
I want to create a chat app with android studio and when I want to display users in my app, app crashed and my code is below:
private void readChats()
{
mUsers = new ArrayList<>();
reference = FirebaseDatabase.getInstance().getReference("Users");
reference.addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(#NonNull DataSnapshot dataSnapshot)
{
mUsers.clear();
for (DataSnapshot snapshot : dataSnapshot.getChildren())
{
User user = snapshot.getValue(User.class);
for (String id : userList){
assert user != null;
if (user.getId().equals(id)) {
if (mUsers.size() != 0) {
for (User user1 : mUsers) {
if (!user.getId().equals(user1.getId())) {
mUsers.add(user);
}
}
}else {
mUsers.add(user);
}
}
}
}
userAdapter = new UserAdapter(getContext(), mUsers);
recyclerView.setAdapter(userAdapter);
}
#Override
public void onCancelled(#NonNull DatabaseError databaseError)
{
}
});
}
and my error is below:
java.util.ConcurrentModificationException
at java.util.ArrayList$Itr.next(ArrayList.java:831)
at com.example.mahdi.chatapp.Fragments.ChatsFragment$2.onDataChange(ChatsFragment.java:101)
and error from this line:
for (User user1 : mUsers)
I can't fix this error please help me:
You can't change this list ("mUsers") in loop, because loop use count items for limits.
If you make temp variable, or use this code:
for (User user : new ArrayList< User >(mUsers)) {
if (!user.getId().equals(user1.getId())) {
}
}
I hope it helpful
If you use plain for loop instead of enhanced for loop, your problem is resolved.
if (mUsers.size() != 0) {
for (User user1 : mUsers) {
if (!user.getId().equals(user1.getId())) {
mUsers.add(user);
}
}
}else {
mUsers.add(user);
}
Aside from the ConcurrentModificationException (which comes from the mUsers.add(user) inside the loop over mUsers), I don't think this is the logic you intend. This would add user to the list N times, where N is the number of users in the list with differing IDs.
I suspect you might want something like:
if (mUsers.stream().noneMatch(u -> user.getId().equals(u.getId())) {
mUsers.add(user);
}
which adds user once, if no other user with that ID is present.
You might also consider using a Map<String, User>, where the key is the user's ID. Then you could use:
map.computeIfAbsent(user.getId(), k -> user);
I was too facing this issue.
Better you can use the below code snippet which will make sure that only one user has been added in the ChatFragment.
reference=FirebaseDatabase.getInstance().getReference("Users");
reference.addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(#NonNull DataSnapshot dataSnapshot) {
mUsers.clear();
for(DataSnapshot snapshot:dataSnapshot.getChildren()){
User user=snapshot.getValue(User.class);
//Display 1 user from chat
for (String id:usersList){
assert user != null;
if(user.getId().equals(id)){
if(mUsers.size()!=0){
int flag=0;
for(User u : mUsers) {
if (user.getId().equals(u.getId())) {
flag = 1;
break;
}
}
if(flag==0)
mUsers.add(user);
}else{
mUsers.add(user);
}
}
}
}
userAdapter=new UserAdapter(getContext(),mUsers,true);
recyclerView.setAdapter(userAdapter);
Thank You
According to the documentation, we should avoid nesting data when it comes to database relationships. So I structured my data as follow:
{
"rides": {
"ride1": {
"price": "500",
"bids": {
// the value here doesn't matter, just that the key exists
"bid1": true,
"bid2": true
}
},
...
},
"bids": {
"bid1": {
"price": "550",
"ride": "ride1"
},
...
}
}
In one page of my app, the user should be able to see all "bids" that are linked to the selected "ride" in a ListView. My attempt was: Getting the keys of the bids in an arrayList and then fetching the bids by key on by one. My problem is the fact that I have to display all the bids in a listview at once.
bidsListener = new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
ArrayList<String> bidskeysOfActiveRideArrayList = new ArrayList<>();
for (DataSnapshot snapshot : dataSnapshot.getChildren()) {
Boolean IsbidLinkedToThisRid = snapshot.getValue(Boolean.class);
if (IsbidLinkedToThisRid) {
String bidObjectId = dataSnapshot.getKey();
bidskeysOfActiveRideArrayList.add(bidObjectId);
}
}
fetchMultipleBidsByKeys(bidskeysOfActiveRideArrayList);
}
#Override
public void onCancelled(DatabaseError databaseError) {
}
};
databaseReference.child("rides").child(ride.getID()).child("bids").addValueEventListener(bidsListener);
The two functions are here:
private void fetchMultipleBidsByKeys(ArrayList<String> bidskeysOfActiveRideArrayList) {
ArrayList<Bid> bidsArrayList = new ArrayList<>();
for (String bid_id:bidskeysOfActiveRideArrayList){
bidsArrayList.add(getBidByKey(bid_id));
}
displayBidList(bidsArrayList);
}
private Bid getBidByKey(String bidObjectId) {
final Bid[] bidFetched = {null};
ValueEventListener bidsSingleFetcher = new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
for (DataSnapshot snapshot : dataSnapshot.getChildren()) {
bidFetched[0] = snapshot.getValue(Bid.class);
}
}
#Override
public void onCancelled(DatabaseError databaseError) {
}
};
databaseReference.child("bids").child(bidObjectId).addListenerForSingleValueEvent(bidsSingleFetcher);
return bidFetched[0];
}
However, firebase data fetch is asyncronuous. So what is the correct way of fetching elements by a list of IDs then displaying them on a listview?
I want to be able to retrieve everything that is currently held in a Firebase child folder. I write to the database like so;
private void commitPost() {
final String commentInput = commentText.getText().toString().trim();
commentProgress.setMessage("Posting");
commentProgress.show();
String uniqueId = UUID.randomUUID().toString();
commentDB.child(postID).child("Comments").child(uniqueId).setValue(commentInput);
commentProgress.dismiss();
finish();
}
I want to be able to read all values that are currently held in the child folder and then eventually display them in a ListView.
Database JSON;
"Engagement" : {
"-Kne46iBe6ooNFKTv_8w" : {
"Comments" : {
"c323835c-290c-44f3-8070-f9febf698ec9" : "none now!",
"stg15QKZFhNmTCYrgL5PtQ4wxJf2" : "testing"
},
"Likers" : {
"stg15QKZFhNmTCYrgL5PtQ4wxJf2" : "email#email.com"
}
To get the data only from a particular node, please use the following code:
DatabaseReference rootRef = FirebaseDatabase.getInstance().getReference();
DatabaseReference engagementRef = rootRef.child("Engagement").child("-Kne46iBe6ooNFKTv_8w").child("Comments");
ValueEventListener eventListener = new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
for(DataSnapshot ds : dataSnapshot.getChildren()) {
String value = ds.getValue(String.class);
Log.d("TAG", value + ", ");
}
}
#Override
public void onCancelled(DatabaseError databaseError) {}
};
engagementRef.addListenerForSingleValueEvent(eventListener);
Your output will be:
none now!, testing,
This is my firebase data Structure.
Now what basically I want to do is, first there is the Id:"cbn". I have pushed the Location with push().setValue(gpsData). Now based on this id = "cbn", I want to add new Latitude and Longitude in the Location child exactly like shown in the figure above by using "push(). Till now my code is:
mDatabase = FirebaseDatabase.getInstance().getReference().child("Users");
if (busExists) {
// busId = "cbn"
mDatabase.child(busId).child("Location").push().setValue(gpsData);
}
I know I cannot reference busId as child but how can I access that particular node and push my new data to it.
Any Help?
For new question:
public class Users {
private String bus_id;
private HashMap<String, String> coord = new HashMap<>();
public Users() {
}
public String getBus_id() {
return bus_id;
}
public void setBus_id(String bus_id) {
this.bus_id = bus_id;
}
public HashMap<String, String> getCoord() {
return coord;
}
public void setCoord(HashMap<String, String> coord) {
this.coord = coord;
}
}
I implemented it this way:
mDatabase = FirebaseDatabase.getInstance().getReference();
mDatabase.addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
for (DataSnapshot ds : dataSnapshot.getChildren()) {
Users bus = new Users();
bus.setBus_id(ds.getValue(Users.class).getBus_id());
Log.e("bus id: ", bus.getBus_id()+""); // I got NULL
busList.add(bus.getBus_id());
arrayAdapter.notifyDataSetChanged();
}
}
#Override
public void onCancelled(DatabaseError databaseError) {
}
});
You'll need to fire a query to get the push ID of cbn and then add the location from there:
Query query = ref.child("Users").orderByChild("Id").equalTo("cbn");
query.addListenerForSingleValueEvent(new ValueEventListener() {
public void onDataChange(DataSnapshot snapshot) {
for (DataSnapshot user: snapshot.getChildren()) {
user.getRef().child("Location").push().setValue(...);
The loop in onDataChange() is needed, since there may be multiple child nodes matching the query. If there can only be one child with a specific Id, consider storing the users under that Id.
Users
cbd
Locations:...
With this structure you can add a new location without first querying:
ref.child("Users/cbn/Location").push().setValue(...);