I have a Firebase db structured like so:
{
"reviews" : {
"Star Trek Into Darkness" : {
"j" : {
"major" : "NONE",
"rating" : 3.0,
"username" : "j"
}
},
"Star Wars" : {
"badabing" : {
"major" : "Computer Science",
"rating" : 3.5,
"username" : "badabing"
},
"j" : {
"major" : "NONE",
"rating" : 5.0,
"username" : "j"
}
},
"Star Wars: The Clone Wars" : {
"j" : {
"major" : "NONE",
"rating" : 3.0,
"username" : "j"
}
},
"Star Wars: The Force Awakens" : {
"badabing" : {
"major" : "Computer Science",
"rating" : 5.0,
"username" : "badabing"
},
"j" : {
"major" : "NONE",
"rating" : 3.0,
"username" : "j"
}
}
}
There are movies under reviews and each movie has a username, major, and rating. I'm interested in getting those three children PER username (the parent of the three). What is supposed to be happening is when a user views the "Movie Details" screen of my app--they will see the reviews left by other users. Simple enough.
I'm having major difficulty with updating my custom ReviewAdapter. Currently, if we click on a movie that doesn't have a review--I can add them no problem. I can even submit an edit to the review I left. The only issue is that the submitted review submits two reviews--so I end up with a duplicate until I leave the activity and come back.
My issue can be seen in this recording here. (No sound)
I suspect it has something to do with the way I'm using Firebase's childEventListener(), but I couldn't figure out what to change after spending 4hrs+ on this issue alone.
MovieDetailActivity.java
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_movie_detail);
ButterKnife.bind(this);
getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
Intent intent = getIntent();
Bundle bundle = intent.getExtras();
if (bundle != null) {
mMovieTitle = (String) bundle.get("title");
mTempMovieTitle.setText(mMovieTitle);
}
setupReviews(); //calling it here
}
public void leaveReview() {
//get current username
final String currentUser = SessionManager.getInstance(MovieDetailActivity.this).getLoggedInUsername();
final MaterialDialog reviewDialog = new MaterialDialog.Builder(MovieDetailActivity.this)
.title(leaveReviewTitle)
.customView(R.layout.rating_movie_dialog, true)
.theme(Theme.DARK)
.positiveText(save)
.negativeText(cancel)
.onPositive(new MaterialDialog.SingleButtonCallback() {
#Override
public void onClick(#NonNull MaterialDialog reviewDialog, #NonNull DialogAction which) {
final RatingBar ratingBar = ButterKnife.findById(reviewDialog, R.id.rating_bar);
final double rating = ratingBar.getRating(); //get the rating
/*Get Major from Firebase, and also store the review while we're at it*/
mUserRef.child(currentUser).addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
String major = dataSnapshot.child("major").getValue(String.class);
final Firebase reviewRef = mReviewRef.child(StringHelper.reviewHelper(mMovieTitle, currentUser));
reviewRef.child("username").setValue(currentUser);
reviewRef.child("major").setValue(major);
reviewRef.child("rating").setValue(rating);
setupReviews(); //and here
}
#Override
public void onCancelled(FirebaseError firebaseError) {
}
});
}
}).build();
//Leave review as {current_username}
TextView reviewee = ButterKnife.findById(reviewDialog, R.id.reviewee);
reviewee.append(" " + (Html.fromHtml("<b>" + currentUser + "</b>"))); //bold the username text
reviewDialog.show();
}
/**
* *ATTEMPTS* to add and update the reviews list per each movie. It's hacky and I hate it.
*/
private void setupReviews() {
mReviewRef.child(mMovieTitle).addChildEventListener(new ChildEventListener() {
#Override
public void onChildAdded(DataSnapshot dataSnapshot, String previousChildKey) {
ArrayList<String> usernames = new ArrayList<>();
ArrayList<String> majors = new ArrayList<>();
ArrayList<Double> ratings = new ArrayList<>();
ArrayList<Review> reviews = new ArrayList<>();
//iterate through all of the reviews for the movie
for (DataSnapshot child : dataSnapshot.getChildren()) {
switch(child.getKey()) {
case ("username"):
usernames.add(child.getValue(String.class));
break;
case ("major"):
majors.add(child.getValue(String.class));
break;
case ("rating"):
ratings.add(child.getValue(Double.class));
break;
}
}
if (!usernames.isEmpty()) { //only want to iterate if we're rating a movie that already has reviews
for (int i = 0; i < usernames.size(); ++i) {
try {
reviews.add(new Review(usernames.get(i), majors.get(i), ratings.get(i)));
} catch (IndexOutOfBoundsException ioobe) {
}
}
}
if (mReviewAdapter == null) {
mReviewAdapter = new ReviewAdapter(MovieDetailActivity.this,
R.layout.review_list_item, reviews);
mMovieReviewsList.setAdapter(mReviewAdapter);
} else {
try {
mReviewAdapter.addAll(reviews);
mMovieReviewsList.setAdapter(mReviewAdapter);
mReviewAdapter.notifyDataSetChanged();
} catch (NullPointerException npe) {
}
}
}
#Override
public void onChildChanged(DataSnapshot dataSnapshot, String previousChildKey) {
ArrayList<String> usernames = new ArrayList<>();
ArrayList<String> majors = new ArrayList<>();
ArrayList<Double> ratings = new ArrayList<>();
ArrayList<Review> reviews = new ArrayList<>();
//iterate through all of the reviews for the movie
for (DataSnapshot child : dataSnapshot.getChildren()) {
switch(child.getKey()) {
case ("username"):
usernames.add(child.getValue(String.class));
System.out.println("username " + child.getValue(String.class));
break;
case ("major"):
majors.add(child.getValue(String.class));
System.out.println("major " + child.getValue(String.class));
break;
case ("rating"):
ratings.add(child.getValue(Double.class));
break;
}
}
if (mReviewAdapter != null) {
mReviewAdapter.clear();
}
if (!usernames.isEmpty()) {
for (int i = 0; i < usernames.size(); ++i) {
try { //I hate that checking if Usernames != empty isn't enough, and this is
//the only way I could get it to work...
reviews.add(new Review(usernames.get(i), majors.get(i), ratings.get(i)));
} catch (IndexOutOfBoundsException ioobe) {
}
}
}
if (mReviewAdapter == null) {
mReviewAdapter = new ReviewAdapter(MovieDetailActivity.this,
R.layout.review_list_item, reviews);
mMovieReviewsList.setAdapter(mReviewAdapter);
} else {
try {
mReviewAdapter.addAll(reviews); //this might break it
mMovieReviewsList.setAdapter(mReviewAdapter);
mReviewAdapter.notifyDataSetChanged();
} catch (NullPointerException npe) {
}
}
}
#Override
public void onChildRemoved(DataSnapshot dataSnapshot) {
}
#Override
public void onChildMoved(DataSnapshot dataSnapshot, String previousChildKey) {
}
#Override
public void onCancelled(FirebaseError firebaseError) {
}
});
}
You would need to use Push method for this to have their own Unique Key. You are using a key with the Username of the logged user. You need to do something like this.
{
"reviews" : {
"Star Trek Into Darkness" : {
"-K3dksosdfify" : {
"major" : "NONE",
"rating" : 3.0,
"username" : "j"
}
},
"Star Wars" : {
"-K3dksosdfify2" : {
"major" : "Computer Science",
"rating" : 3.5,
"username" : "badabing"
},
"-K3dksosdfify4" : {
"major" : "NONE",
"rating" : 5.0,
"username" : "j"
}
},
"Star Wars: The Clone Wars" : {
"-K3dksosdfify10" : {
"major" : "NONE",
"rating" : 3.0,
"username" : "j"
}
},
"Star Wars: The Force Awakens" : {
"-K3dksosdfify11" : {
"major" : "Computer Science",
"rating" : 5.0,
"username" : "badabing"
},
"-K3dksosdfify123" : {
"major" : "NONE",
"rating" : 3.0,
"username" : "j"
}
}
}
The Dashes is FireBase Generated. Check the push method in the Official Document
Now everytime you use push. It will generate a Unique key so The property of the other Rating will not be disturbed.
so something like this.
Firebase ref = new Firebase(yourURLtoReviews);
ref.child(SELECTEDMOVIES);
Map<String, String> post1 = new HashMap<String, String>();
post1.put("major", "gracehop");
post1.put("rating", "3.0");
post1.put("username", "Hwoarang");
ref.push().setValue(post1);
Now you don't need to care about the keys anymore. All you have to do is
ref.onChildAdded blah blah blah, itterate it with for loop and you're done.
Related
Firebase data structure
{ "questions" : {
"-KkLGS71meu8aA_wcqTq" : {
"q_answer" : "....",
"q_category" : "....",
"q_qid" : "1",
"q_question" : "..."
},
"-KkLGkf8tNb-OKwu2qVa" : {
"q_answer" : "...",
"q_category" : "...",
"q_qid" : "2",
"q_question" : "..."
},
"-KkLH1T1ZhFKJaqYFuJ4" : {
"q_answer" : "...",
"q_category" : "...",
"q_qid" : "3",
"q_question" : "..."
} }
Database refrence
questionRef.orderByChild("q_qid")
how do i generate random query and display with FirebaseRecyclerAdapter?
Is there any other way to do it?
Edit :
I try this code
questionRef.addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
long allNum = dataSnapshot.getChildrenCount();
int maxNum = (int)allNum;
int minNum = 1;
int randomNum = new Random().nextInt(maxNum - minNum + 1) + minNum;
int count = 0;
Iterable<DataSnapshot> ds = dataSnapshot.getChildren();
Iterator<DataSnapshot> ids = ds.iterator();
Map<String, Object> question;
while(ids.hasNext() && count < randomNum) {
question = (Map<String, Object>) ids.next().getValue();
String q = question.get("q_question").toString();
viewHolder.question.setText(q);
count ++; // used as positioning.
}
}
#Override
public void onCancelled(DatabaseError databaseError) {
}
});
But some question appear more than once ?
What you need to do, is to find out the number of possible values, numVals in the database and then use that in the following line of code:
int random = new Random().nextInt(numVals);
If you are using in your code an iterator to iterate a DataSnapshot than just iterate a random number of times to get the required value.
Hope it helps.
I have below structure for a table in my application with Firebase Database
"storePurchase":{
"$user_id":{
".read": "$user_id===auth.uid",
".write":"$user_id===auth.uid",
"$storeId":{
".validate":"root.child('stores/'+auth.uid+'/'+$storeId).exists()",
"$pId":{
"cId":{
".validate":"root.child('customers/'+auth.uid+'/'+newData.val()).exists()",
},
"amount":{
},
"rate":{
},
}
}
}
},
that stores data as below:
{
"-KeyN0kMSGZbb9hb9lNm" : {
"-Kf2_5kJoLctmlGcR4d1" : {
"amount" : "2500",
"cid" : "-KdChbdoV7nxwZoGLWsY", //cust1
"rate" : "5"
},
"-Kf2_e05toGelnjRMbfg" : {
"amount" : "10000",
"cid" : "-KdCggxld52mq0DsGRjH", //cust2
"rate" : "0.5"
},
"-Kf2auWq_hQtTErp55SY" : {
"amount" : "5869",
"cid" : "-KdChbdoV7nxwZoGLWsY", //cust1
"rate" : "58"
},
"-Kf2b1QoF4I7LAVAL0_4" : {
"amount" : "25000",
"cid" : "-KdChbdoV7nxwZoGLWsY", //cust1
"rate" : "0.8"
},
"-Kf2bEwN5PtwGUec_UFh" : {
"amount" : "2500",
"cid" : "-KdCggxld52mq0DsGRjH", //cust2
"rate" : "0.8"
},
"-Kf2b_tZo3rVJbegZNXa" : {
"amount" : "5000",
"cid" : "-KdCgJKh6_Rb8MOi-fIj", //cust3
"rate" : "1.5"
}
}
}
I am having cId i.e. customer ID and now I want to combine these data of each customer and store it in a separate table as unique values. How could I combine these data? I have tried using for loop and tried storing these values on addValueEventListener into a separate list, but couldn't get succeeded? How else can I combine these data based on cId and sum up the amount?
EDIT
Am trying to achieve below data structure.
"tallyBook":{
"$user_id":{
".read": "$user_id===auth.uid",
".write":"$user_id===auth.uid",
"$storeId":{
".validate":"root.child('stores/'+auth.uid+'/'+$storeId).exists()",
"cId":{
".validate":"root.child('customers/'+auth.uid+'/'+newData.val()).exists()",
"totalRate":{
},
"totalAmt":{
},
},
}
}
}
Running this transaction every time that a purchase is placed should keep your tallybook up to date in the way that you want it. So run this transaction every time you push a purchase to Firebase.
dbRef.child("tallyBook/"+FirebaseAuth.getInstance().getCurrentUser().getUid()+"/"+storeId+"/"+purchase1.cid).runTransaction(new Transaction.Handler() {
#Override
public Transaction.Result doTransaction(MutableData mutableData) {
TallyBook tally = mutableData.getValue(TallyBook.class);
if (tally == null) {
tally = new TallyBook();
tally.totalRate = purchase1.rate;
tally.totalAmount = purchase1.amount;
} else {
tally.totalRate = String.valueOf(Double.parseDouble(tally.totalRate) + Double.parseDouble(purchase1.rate));
tally.totalAmount = String.valueOf(Double.parseDouble(tally.totalAmount) + Double.parseDouble(purchase1.amount));
}
mutableData.setValue(tally);
return Transaction.success(mutableData);
}
#Override
public void onComplete(DatabaseError databaseError, boolean b, DataSnapshot dataSnapshot) {}
});
}
With Purchase
public class Purchase {
public String rate;
public String cid;
public String amount;
public Purchase() {
}
}
And TallyBook
public class TallyBook {
public String totalRate;
public String totalAmount;
public TallyBook() {
}
}
Then you can use the following query to get the tallybook for a particular user and store.
dbRef.child("tallyBook/"+FirebaseAuth.getInstance().getCurrentUser().getUid()+"/"+storeId).addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
for (DataSnapshot tally : dataSnapshot.getChildren()) {
// I called this TallyBook but it's more like one entry in a tallybook
TallyBook tally = tally.getValue(TallyBook.class);
// Do something with tally, display it or whatever
}
}
#Override
public void onCancelled(DatabaseError databaseError) {
}
});
I am pulling data from a username structure and I would like to sort by name or username. Is this possible with the new firebase database reference. I have been looking around and I cannot find anything about it.
here is my code:
databaseReference = FirebaseDatabase.getInstance().getReference().child("Users");
}
#Override
protected void onStart() {
super.onStart();
FirebaseRecyclerAdapter<addFriends,addFriendsViewHolder> firebaseRecyclerAdapter = new FirebaseRecyclerAdapter<addFriends, addFriendsViewHolder>(
addFriends.class,
R.layout.addfriends_row,
addFriendsViewHolder.class,
databaseReference
) {
#Override
protected void populateViewHolder(addFriendsViewHolder viewHolder, addFriends model, int position) {
viewHolder.setName(model.getName());
viewHolder.setUsername(model.getUsername());
}
};
addfriendsRecyclerView.setAdapter(firebaseRecyclerAdapter);
}
here is my structure in firebase:
{
"asdadafdsgsaasfsa" : {
"name" : "juanito",
"username" : "cacadeperro22"
},
"dasdsadadaf" : {
"name" : "cucu",
"username" : "dsadadas"
},
"dsadasddsa" : {
"name" : "username",
"username" : "pisad"
},
"dsdsada" : {
"name" : "name",
"username" : "username"
},
"qUYxycDlMwYZ6r2WSA1TAacbdPh1" : {
"email" : "elvis0288#hotmail.com",
"name" : "Elvis De Abreu",
"username" : "elvis0288"
},
"qadfdafa" : {
"name" : "Josue Caca",
"username" : "chupapenes"
},
"x123" : {
"name" : "fsf",
"username" : "dadfasfd"
},
"xxxxxx" : {
"name" : "123",
"username" : "culo"
}
}
Thanks!
EDIT: Had stepped away and came back to finish answer. Saw someone beat me to it :)
Pass in a QueryRef in your FirebaseRecyclerAdapter constructor instead of the database reference.
databaseReference = FirebaseDatabase.getInstance().getReference().child("Users");
// ...
#Override
protected void onStart() {
super.onStart();
Query query = databaseReference.orderByChild("name"); // or ...orderByChild("username");
FirebaseRecyclerAdapter<addFriends,addFriendsViewHolder> firebaseRecyclerAdapter = new FirebaseRecyclerAdapter<addFriends, addFriendsViewHolder>(
addFriends.class,
R.layout.addfriends_row,
addFriendsViewHolder.class,
query
) {
#Override
protected void populateViewHolder(addFriendsViewHolder viewHolder, addFriends model, int position) {
viewHolder.setName(model.getName());
viewHolder.setUsername(model.getUsername());
}
};
addfriendsRecyclerView.setAdapter(firebaseRecyclerAdapter);
}
I'm trying to create a list that only contains items of a particular group. For example, I've created/written a "fruits" node and a "groups" node myself in the database console (including the groups' keys).
Relatively new to Firebase, so still trying to wrap my ahead around flattening data structures.
The JSON (showing "fruits" and "groups") looks like the following:
{
"fruits" : {
"apple" : {
"color" : "appleColorValueHere",
"groups" : {
"GroupABCKey" : true,
"GroupXYZKey" : true
},
"origin" : "appleOriginValueHere",
"size" : "appleSizeValueHere"
},
"orange" : {
"color" : "orangeColorValueHere",
"groups" : {
"GroupABCKey" : true,
"GroupXYZ" : true
},
"origin" : "orangeOriginValueHere",
"size" : "orangeSizeValueHere"
},
"strawberry" : {
"color" : "strawberryColorValueHere",
"groups" : {
"GroupJKLKey" : true
},
"origin" : "strawberryOriginValueHere",
"size" : "strawberrySizeValueHere"
}
},
"groups" : {
"GroupABCKey" : {
"members" : {
"apple" : true,
"orange" : true
},
"name" : "Group ABC Name Here"
},
"GroupJKLKey" : {
"members" : {
"strawberry" : true
},
"name" : "Group JKL Name Here"
},
"GroupXYZKey" : {
"members" : {
"apple" : true,
"orange" : true
},
"name" : "Group XYZ Name Here"
}
} ...
...
}
Within the app itself, a user can create a list which copies the entire fruits node into their newly created list (see JSON further down for user-list-items). I'm then displaying the items using FirebaseRecyclerAdapter
Here is the code for how I'm doing that:
private void writeNewFruitList(String userId, String username, String email, String title) {
final String key = databaseReference.child("fruit-lists").push().getKey();
UserLists userLists = new UserLists(userId, username, email, title);
HashMap<String, Object> updatedListToAddMap = new HashMap<>();
HashMap<String, Object> itemToAdd =
(HashMap<String, Object>) new ObjectMapper().convertValue(userLists, Map.class);
updatedListToAddMap.put("/fruit-lists/" + key, itemToAdd);
updatedListToAddMap.put("/user-fruit-lists/" + userId + "/" + key, itemToAdd);
databaseReference.updateChildren(updatedListToAddMap);
final DatabaseReference subDatabaseReference = FirebaseDatabase.getInstance().getReference();
subDatabaseReference.child("fruits").addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
for (DataSnapshot child : dataSnapshot.getChildren()) {
Log.d(TAG, child.getKey());
Log.d("fruitValues::", dataSnapshot.getValue().toString());
final String fruitKey= child.getKey();
FruitModel fruitModel = child.getValue(FruitModel.class);
Map<String, Object> nodeValues = fruitModel.toMap();
Map<String, Object> childUpdates = new HashMap<>();
String new_path = "/user-list-items/" + key + "/" + fruitKey+ "/";
childUpdates.put(new_path, nodeValues);
subDatabaseReference.updateChildren(childUpdates);
}
}
#Override
public void onCancelled(DatabaseError databaseError) {
Log.e(TAG, "onCancelled", databaseError.toException());
}
});
}
Q: How can I implement this process using groups? E.g., a user creates a list and only wants the newly created list to contain fruits that are of GroupABCKey but they also want the details of those fruits (size, origin etc).
Q2: What's the best way to edit my FruitModel.java to account for the groups?
public class FruitModel {
public String size;
public String origin;
public String color;
public FruitModel() {
}
public FruitModel(String size, String origin, String color) {
this.size = size;
this.origin = origin;
this.color = color;
}
#Exclude
public Map<String, Object> toMap() {
HashMap<String, Object> result = new HashMap<>();
result.put("size", size);
result.put("origin", origin);
result.put("color", color);
return result;
}
// getters & setters
}
Finally, here's what I believe the user-list-items node would look like if done correctly?
listKey1forUserA is what I am creating correctly (except for mapping the groups as mentioned previously).
listKey2forUserA is what I'm trying to accomplish - A user wants to create and populate a list only with items that are in "GroupJKLKey"
"user-list-items" : {
"listKey1forUserA" : {
"apple" : {
"color" : "appleColorValueHere",
"groups" : {
"GroupABCKey" : true,
"GroupXYZ" : true
},
"origin" : "appleOriginValueHere",
"size" : "appleSizeValueHere"
},
"orange" : {
"color" : "orangeColorValueHere",
"groups" : {
"GroupABCKey" : true,
"GroupXYZKey" : true
},
"origin" : "orangeOriginValueHere",
"size" : "orangeSizeValueHere"
},
"strawberry" : {
"color" : "strawberryColorValueHere",
"groups" : {
"GroupJKLKey" : true
},
"origin" : "strawberryOriginValueHere",
"size" : "strawberrySizeValueHere"
}
},
"listKey2forUserA" : {
"strawberry" : {
"color" : "strawberryColorValueHere",
"groups" : {
"GroupJKLKey" : true
},
"origin" : "strawberryOriginValueHere",
"size" : "strawberrySizeValueHere"
}
}
}
Is this the proper way to go about this? Any help or a point in the right direction would be great.
Although my usage has slightly changed since I originally posted this question, the two use cases below are still applicable with some minor adjusting. There's probably a better way to go about this, so I'd definitely welcome any suggestions or ideas from others.
The tl;dr is that you structure your database in such a way that you have a groups path and a users path. The users path contains no reference to groups. Get a datasnapshot of the children of a particular group (just need the keys) and add them to an ArrayList. > For each item in your ArrayList, plug that into the users path.
This will then allow access to the details of those users (or fruits, as used in my original question) and the ability to display that information or create new information however you see fit.
Create/Write a list of people based on group membership:
private void writeListFromGroupMembership(){
final DatabaseReference mainPathReference = FirebaseDatabase.getInstance().getReference();
final ArrayList<String> groupMembers = new ArrayList<>();
DatabaseReference groupsReference = FirebaseDatabase.getInstance().getReference()
.child("groups")
.child("someOtherChildYouMayHave")
.child("nameOfGroupYouAreInterestedIn");
/* Step 1: Get a list of people in a specific group that you are interested in */
groupsReference.addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
for (DataSnapshot child : dataSnapshot.getChildren()) {
String groupMemberKey = child.getKey();
if (child.getValue(Boolean.class) != null) {
groupMembers.add(groupMemberKey);
} else {
// TODO
}
}
/* Step 2: Loop through your list of people and insert them as a child like the following: */
for (int i = 0; i < groupMembers.size(); i++) {
final String singleGroupMember = groupMembers.get(i);
mainPathReference.child("all-groups").child("anotherChildYouMayHave").child(singleGroupMember)
.addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
// Make a POJO for the item and immediately turn it into a HashMap
Person person= dataSnapshot.getValue(Person.class);
Map<String, Object> nodeValues = person.toMap();
// Make a map for the items you are adding
Map<String, Object> childUpdates = new HashMap<>();
// The path where the items will be copied/written to.
String newPath = "/master-list-items/" + listKey + "/" + singleGroupMember + "/";
// Add the items to the update map
childUpdates.put(newPath, nodeValues);
// Do the update
mainPathReference.updateChildren(childUpdates);
}
#Override
public void onCancelled(DatabaseError databaseError) {
Log.w(TAG, "onCancelled: ", databaseError.toException());
}
});
}
}
// your other onCancelled override method here
}
Display a list of people based on group membership (using a Spinner, for example) :
public List<Person> persons;
...
private void getGroupMembers(String groupSelected) {
groupRef= FirebaseDatabase.getInstance().getReference()
.child("someOtherChildThatYouHouseGroupsUnder")
.child(groupSelected)
.child("members");
groupRef.addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
for (DataSnapshot child : dataSnapshot.getChildren()) {
String groupMember= child.getKey();
selectedGroupMembers.add(groupMember);
}
for (int i = 0; i < selectedGroupMembers.size(); i++) {
String singleMember = selectedGroupMembers.get(i);
singleMemberDatabaseReference = FirebaseDatabase.getInstance().getReference()
.child("all-people")
.child(singleMember);
singleMemberDatabaseReference .addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
Person person = dataSnapshot.getValue(Person.class);
persons.add(person);
personAdapter = new PersonAdapter(getApplicationContext(), persons);
recyclerView.setAdapter(personAdapter);
}
#Override
public void onCancelled(DatabaseError databaseError) {
Log.w(TAG,"onCancelled: ", databaseError.toException());
}
});
}
}
#Override
public void onCancelled(DatabaseError databaseError) {
Log.w(TAG, "onCancelled: ", databaseError.toException());
}
});
}
For displaying a list of people based on group membership, my database structure is similar to:
{
"all-groups":{
"groupNameorKeyOfGroupName":{
"details":{
"meetingLocation":"110 William Street",
"someOtherChild":"Your value"
},
"members":{
"fakeUserA":true,
"fakeUserB":true,
"fakeUserC":true,
"fakeUserD":true
}
}
}
"all-users":{
"fakeUserA":{
},
"fakeUserB":{
}
}
}
I have been cracking my head to solve this problem and now I have decided to ask the community. I have a list of Locations in my Firebase Database where only selected users can access. Here is a snippet of my Firebase Database:
"Locations" : {
"Location1" : {
"Users" : "UID1",
"location" : "Alam Megah",
"stationNumber" : 1122
},
"Location2" : {
"Users" : "UID1",
"location" : "Ampang 1",
"stationNumber" : 1134
},
"Location3" : {
"Users" : "UID2",
"location" : "Bukit Bintang 1",
"stationNumber" : 1130
},
"Location4" : {
"Users" : "UID2",
"location" : "London",
"stationNumber" : 1130
},
"Location5" : {
"Users" : "UID3",
"location" : "Paris",
"stationNumber" : 1130
},
"Location6" : {
"Users" : "UID3",
"location" : "Singapore",
"stationNumber" : 1130
}
},
"Users" : {
"UID1" : {
"UserID" : "User1",
"Location" : {
"Location1":true,
"Location2":true
},
"Email" : "user1#abc.com"
},
"UID2" : {
"UserID" : "User2",
"Location" : {
"Location3":true,
"Location4":true
},
"Email" : "user2#abc.com"
},
"UID3" : {
"UserID" : "User3",
"Location" : {
"Location5":true,
"Location6":true
},
"Email" : "user3#abc.com"
}
I have written security rules as below so that only selected users can access certain location.
{
"rules":{
"Users":{
"$uID":{
".read":"auth != null && auth.uid === $uID",
".write":"auth !=null && auth.uid === $uID"
}
},
"Locations":{
"$Location":{
".read":"auth != null && (auth.isAdmin==true || root.child('Users').child(data.child('Users').val()).child('UserID').val()==auth.uid)"
}
}
}
}
I have tried running this code in Firebase Simulator in the console under the 'Rules' tab and it works as I want it to. But, when I want to populate this in my listview in Android using the Firebase List Adapter, I can't do it as before because now, I do not have permission to access "Locations" node. And if I grant access to "Locations" node, all childs of "Locations" automatically gain access too. Here are snippets of my Locations, Location List Adapter and LocationActivity classes:
Locations.class:
public class Locations {
private String mLocation;
private int mStationNumber;
#Exclude
private String mUser;
public Locations() {
}
public Locations(String location, int stationNumber) {
mLocation = location;
mStationNumber = stationNumber;
}
public String getLocation() {
return mLocation;
}
public long getStationNumber() {
return mStationNumber;
}
}
LocationListAdapter.class
public class LocationListAdapter extends FirebaseListAdapter<Locations> {
public LocationListAdapter(Activity activity, Class<Locations> modelClass, int modelLayout, Query ref) {
super(activity, modelClass, modelLayout, ref);
this.mActivity = activity;
}
#Override
protected void populateView(View v, Locations model, int position) {
TextView locationName = (TextView) v.findViewById(R.id.locationName);
TextView locationID = (TextView) v.findViewById(R.id.locationID);
locationName.setText(model.getLocation());
locationID.setText(Integer.toString((int) model.getStationNumber()));
}
}
LocationSelectorActivity.class
public class LocationSelectorActivity extends AppCompatActivity {
private ListView mListView;
private LocationListAdapter mLocationListAdapter;
public LocationSelectorActivity() {
}
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
DatabaseReference locationRef = FirebaseDatabase.getInstance().getReference().child("Locations");
mListView = new ListView(this);
mLocationListAdapter = new LocationListAdapter(this, Locations.class,R.layout.single_location_list,locationRef);
mListView.setAdapter(mLocationListAdapter);
setContentView(mListView);
mListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
Locations selectedLocation = mLocationListAdapter.getItem(position);
if(selectedLocation!=null){
Intent intent = new Intent(LocationSelectorActivity.this, ChecklistSelectorActivity.class);
String listId = mLocationListAdapter.getRef(position).getKey();
intent.putExtra("location",listId);
startActivity(intent);
}
}
});
}
}
My question is, can I use another method, if available, to iterate through the list of "Locations" and populate using the rules above?
I know from my readings that I cannot use rules as a filter, so what I thought of doing is to have a list of different Location groups, say Location1, Location2 and Location3, each having their own list of users and Locations as children, and consequently create different POJOs and ListAdapters in my Java code, but then, I feel this is redundant as most of the work is done in client side itself. I may be wrong so please correct me if there are other methods.
Thank you very much for your help.