I've the following scenario:
One user can have several photos, each photo can be reported by other users for violating the term of the app
So i do something like:
FirebaseDatabase.getInstance().getReference().child("reports").child(ownerId).child(g.getMediaId()).addListenerForSingleValueEvent(new ValueEventListener() {
...
...
public void onDataChange(#NonNull DataSnapshot dataSnapshot) {
if (!dataSnapshot.exists()) { //do stuff
}
}
The point is dataSnapshot.exists() returns if the whole branch exists... it may not exist because the mediaID is new (first report of that photo) or because the whole ownerId branch is new (first report of any photo of that user)
how can i know if the parent exists?
Just to summarize what I understand:
You have a JSON tree /reports/$ownerid/$mediaid.
You attach a listener to a specific /reports/ownerid/mediaid.
You want to check if the ownerid exists.
A listener gets all the data underneath the level where you attach it, but it gets no data from levels above where it is attached. So the only way to determine if /reports/ownerid exists, is to attach a listener on that level.
Note that this will then download all data under that /reports/ownerid, which may be more than you need. If that is the case, consider keeping a separate list of known_owners, where you track which owners you have data for.
Related
I'm trying to build a simple shopping list android app with a firebase database.
Quick context:
User registers with email and should be able to create a group to share a shopping list that belongs to the group (and all the members of the group). And then later on shopping list items are added to the shopping list.
I'm a bit stuck on how to handle the many-to-many relationship between the users and the groups.
I read this post: Many to Many relationship in Firebase
And based on that I created this data structure in Firebase:
What I don't really understand that how I will query this exactly. As we use value event listeners in Firebase, my guess was that I would need to attach the listener to the database reference and save the related data locally in the app to populate the screens.
For example, I would like a recycler view with all the groups that belong to the user, where the cards would display the name of the group, the creation date of the group, and the group members. Based on the above data structure, that's 4 separate value event listeners nested into each other, is this correct?
This is part of the code that I have written and this is only saving the userIDs for each group so it would require another nested query:
//get group ids - there is at least one as own_group is set up with the user details
Utils.userGroupReference.child(Utils.firebaseUserID).addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(#NonNull DataSnapshot snapshot) {
for (DataSnapshot groupIDSnapshot : snapshot.getChildren()) {
Utils.myGroupIDs.add(groupIDSnapshot.getKey());
}
for (int i = 0; i < Utils.myGroupIDs.size(); i++) {
//get groups based on group ids
Utils.groupReference.child(Utils.myGroupIDs.get(i)).addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(#NonNull DataSnapshot snapshot) {
Group group = (snapshot.getValue(Group.class));
GroupUser groupUser = new GroupUser(group);
Utils.myGroups.add(group);
Utils.groupUserReference.child(snapshot.getKey()).addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(#NonNull DataSnapshot groupUserSnapshot) {
ArrayList<String> userIDs = new ArrayList<>();
for (DataSnapshot userIDSnapshot: groupUserSnapshot.getChildren()) {
userIDs.add(userIDSnapshot.getKey());
}
Utils.myGroupUsers.add(new GroupUser(group, userIDs));
}
#Override
public void onCancelled(#NonNull DatabaseError error) {
}
});
Utils.myGroupUsers.add(groupUser);
}
#Override
public void onCancelled(#NonNull DatabaseError error) {
}
});
I would appreciate it if someone could shed some light on this for me.
Another question is where do we normally call the value event listeners? This snippet is from the onCreate() method in the MainScreen which eventually would contain all the shopping lists. I was thinking to put it in the splash screen but my experience is that whenever a node is updated, the application will get thrown back to the screen that contains the value event listener, so I'm not sure which is a better option
Thank you very much!
I would typically use addListenerForSingleValueEvent listeners or getData calls for some of the inner data loading here, but it is correct that you'll need a separate call for each piece of data you load.
Since Firebase pipelines all these requests over a single connection, the data is loaded really quickly - so it's really just the code that gets a bit convoluted.
If that is something you'd like to prevent, you could consider duplicating some of the data from the User into each GroupUser and/or UserGroup entry where that user is present too. This will make the code that writes the data more complex, but simplifies the data loading.
This sort of trade-off is quite common when dealing with NoSQL databases, and is one of the reasons they can scale to so many users. To learn more about this, I recommend checking out NoSQL data modeling, Firebase for SQL developers, and Getting to know Cloud Firestore (the latter is for Cloud Firestore, but many of the lessons also apply here).
I know there are lot of questions asked on Firebase Datasnapshot, but still i'm little unclarified about my doubt.
Here is the thing,
I've data structer like this in Firebase Realtime database,
If i have a 100kb file inside the above reference say,
val ref = FirebaseDatabase.getInstance().getReference("Groups")
If i call
`
ref.addValueEventListener(object : ValueEventListener {
override fun onCancelled(p0: DatabaseError) {
}
override fun onDataChange(p0: DataSnapshot) {
// access getvalue here
}
})
`
I get a snapshot of data inside the reference as p0.
Question-1
Will this particular snapshot has all the available data inside the reference (will this downloaded snapshot size is 100kb?). If i cut the internet at the first line on onDataChange, can i be able to access all the data inside datasnapshot Or Will the data be downloaded only when i call p0.getValue command on onDataChange & datasnapshot is just like data reference?
Question-2
In my code i've put a ValueEventListener on "Groups", so if there is a single change in the person2 activity(either new activity is added or existing data is modified), I'm downloading the entire data in the "Groups", which i wanted to avoid as the groups become larger more data is consumed. How to effectively get data from only person2 when person2 data modified or new activity added. I've tried adding child event listener to "Groups", which behaves same as ValueEventListener. I can't add listener for each person as the number of persons are unknown and it can be a larger number, i dont wan't that many listener in my code. My code is in production already so i can't even change the database structure.
Yes, the snapshot will contain everything in memory at the moment you receive it in your callback. You don't even have to reach into the snapshot at all.
With ValueEventListener, if anything under the snapshot changes while the listener is attached, only the changes will be transferred. The entire snapshot is not downloaded entirely for each change to a child. However, you will have to write code to determine exactly what changed in the snapshot. This could be difficult, as you will have to compare all the child values to each other from the prior and new snapshot.
I am currently getting a single dataSnapshot from Firebase like so:
public Task<DataSnapshot> getLatestMessage(#NonNull String roomId) {
final TaskCompletionSource<DataSnapshot> source = new TaskCompletionSource<>();
DatabaseReference dbRef = mDatabase.getReference(NODE_MESSAGES).child(roomId);
dbRef.keepSynced(true);
ValueEventListener listener = new ValueEventListener() {
#Override
public void onDataChange(#NonNull DataSnapshot dataSnapshot) {
source.setResult(dataSnapshot);
}
#Override
public void onCancelled(#NonNull DatabaseError databaseError) {
source.setException(databaseError.toException());
}
};
Query query = dbRef.orderByKey().limitToLast(1);
query.addListenerForSingleValueEvent(listener);
return source.getTask();
}
Notice that I called keepSynced() on dbRef object already.
Here's the sample data structure:
/root
/messages
/$roomId
/$messageId
/content
/timestamp
/etc...
I am able to get the most recent single snapshot data as expected, but I was wondering, does it make any difference if I move the keepSynced() call in the Query object instead of the DatabaseReference? i.e.
// dbRef.keepSynced(true); >> REMOVE THIS <<
ValueEventListener listener = new ValueEventListener() {...};
Query query = dbRef.orderByKey().limitToLast(1);
query.keepSynced(true); // >> ADD THIS <<
query.addListenerForSingleValueEvent(listener);
We're currently averaging 50% load (per day) on Firebase right now and with the steady inflow of users, I was wondering if it could improve anything in the app somehow, specially with the load. I even tried something as silly as this:
dbRef.keepSynced(true);
ValueEventListener listener = new ValueEventListener() {...};
Query query = dbRef.orderByKey().limitToLast(1);
query.addListenerForSingleValueEvent(listener);
dbRef.keepSynced(false);
-- enabling keepSynced() at the start to make sure that the reference is pointing to the most recent version, then disabling it after querying and adding the listener. Unfortunately, this doesn't provide the fresh data, not like when keeping it enabled.
I've already gone through the Optimization DB Performance documentation and believe that I followed the suggested practices as needed.
Putting the keepSynced() call on a limitToLast() query will not make any difference on the load on the database server. The server needs to load the exact same data and monitor it, it just only returns the last item to the client.
I recommend using keepSynced sparingly in your app. Each call to keepSynced keeps an empty listener to the reference/query you attach it to. That means that each client has an active listener to each chat room you call keepSynced on, even when the user is not looking at that room. While that may be precisely the right call for the use-cases of your app, it will limit the scalability of your app.
If you're worried about reaching peak load, you might want to consider looking into how to shard your data over multiple databases. Chat apps are typically relatively easy to shard, since each chat room is already isolated.
I want to get the data stored in the DB without being restricted to access it only when there is a data change.
I've seen this post from 2016:
How to access Firebase data without using addValueEventListener
Which suggested to use addValueEventListener.
I've also seen this post:
Accessing data in Firebase databse
Without good answer.
ValueEventListener will trigger the onDataChange only when the database will have a change.
How else can I access the database without something being changed in the database?
For now I will write simple harmless change in order to access the data, but i'm wondering if it's the only way to do it.
Thanks
Of course this is absolutely not true. You can retrieve data whenever you like to.
Firstly I would like to advice you to read this documentation reference.
Secondly I provide you with what you really asked for.
If you read the documentation you will notice that it states the following:
The onDataChange() method in this class is triggered once when the listener is attached and again every time the data changes, including the children.
That means that with this code:
databaseReference.removeEventListener(eventListener);
With that method you would be able to detatch any listener so it only listens once or detatch it whenever you want to.
There is a method for only retrieving data once though.
databaseReference.addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
Log.d(TAG, "Data retrieved.");
}
...
}
This method will exactly call onDataChange once or respectively onCancelled.
I'm trying out Firebase as my backend for a prototype app im creating.
As a mockup, im creating a fake game.
The data is pretty simple:
There are 3 lists of 'levels' common to all users, organized by difficulty ( easy, medium hard ). ( These are in a Fragment inside a ViewPager
Each level has mini-games inside of them than the users complete.
On the Android app, the user sees that list but also sees a counter of how many mini-games of that level he/she has completed.
If the user clicks on a level, he/she sees the list of mini-games and sees which of those are completed.
Im currently structuring my data as follows:
levels:{
easy:{
easy-level-1-id:{
total-mini-games:10,
easy-level-1-id:String
}
},
medium:{....},
hard:{.....}
}
user-progress:{
user-id:{
levels:{
easy-level-1-id:{
user-completed:int
}
},
mini-games:{
mini-game-1:true
mini-game-2:true
}
}
}
I have to access many places in order to check if a game has been completed, or how many games have been completed per level.
All of this is in order to avoid nesting data, as the docs recommend.
Is it better to do it like this or is it better to store every available level under every user id in order to do less calls, but having much more repeated data ?
I know about the FirebaseRecyclerViewAdapter class provided by the Firebase team, but because it takes a DatabaseReference object as an argument for knowing where the data, it doesn't really apply here, because i fetch data from different structures when building the model for the lists.
mRef1.child(some_child).addValueEventListener(new ValueEventListener(){
#Override
public void onDataChange(DataSnapshot dataSnapshot){
final Model model = dataSnapshot.getValue(Model.class);
mRef2.child(some_other_child).addValueEventListener(new ValueEventListener(){
#Override
public void onDataChange(DataSnapshot dataSnapshot){
//add more data into the model class
.....
}
#Override
public void onCancelled(DatabaseError databaseError){}
});
#Override
public void onCancelled(DatabaseError databaseError){}
});
Any pointers as to how to work with more structured data ?
FirebaseUI includes support for working with indexed data. It can load linked data from an index, such as in your mini-games node.
In your example that could work as:
keyRef = ref.child("user-progress").child(currentUser.getUid()).child("mini-games");
dataRef = ref.child("mini-games");
adapter = new FirebaseIndexRecyclerAdapter<Chat, MiniGameHolder>(
MiniGame.class,
android.R.layout.two_line_list_item,
MiniGameHolder.class,
keyRef, // The Firebase location containing the list of keys to be found in dataRef.
dataRef) //The Firebase location to watch for data changes. Each key key found at keyRef's location represents a list item in the RecyclerView.