Firebase Android: Having issues when my database grows - android

During testing I had about 30 quotes in my data base and about 6 users.
i now added rest of my quote.
Total is about 2000:
Problem:
It takes so long for the app to load, it crashes and goes to the page wait for the app or exit. Then I cut down to 200 quotes, but having issue still (might show one quote before crashing)
What i am doing?
I am referencing my quotes in my database and getting a random quote every time someone open the main page (Did not know how to get one quote a day)
Snippet of JSON for the quotes:
{
"Quotes": {
"1": {
"Name": "Abbey, Edward",
"Quote": "In social institutions, the whole is always less than the sum of its parts. There will never be a state as good as its people, or a church worthy of its congregation, or a university equal to its faculty and students."
},
"2": {
"Name": "Adams, George Matthew",
"Quote": "There is no such thing as a self-made man. We are made up of thousands of others. Everyone who has ever done a kind deed for us, or spoken one word of encouragement to us, has entered into the makeup of our character and our thoughts, as well as our success."
},
"3": {
"Name": "Albani, Emma",
"Quote": "I had always loved beautiful and artistic things, though before leav"
},
"4": {
"Name": "Borman, Frank",
"Quote": "Exploration is really the essence of the human spirit."
},
........ etc
my code:
private void showQuote(){
databaseReference = FirebaseDatabase.getInstance().getReference("Quotes");
databaseReference.addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(#NonNull DataSnapshot dataSnapshot) {
int count = (int) dataSnapshot.getChildrenCount();
for(DataSnapshot data: dataSnapshot.getChildren()){
int rand = new Random().nextInt(count);
for (int i = 0; i < rand; i++) {
String authorName = data.child("Name").getValue().toString();
String quoteGiven = data.child("Quote").getValue().toString();
name.setText("- " + authorName);
quote.setText(quoteGiven);
}
}
}
#Override
public void onCancelled(#NonNull DatabaseError databaseError) {
Toast.makeText(MainActivity.this, "Loading Quote failed", Toast.LENGTH_SHORT).show();
}
});
}
My Rules:
{
"rules": {
"users": {
"$uid": {
".read": "auth != null && auth.uid == $uid",
".write": "auth != null && auth.uid == $uid"
}
},
"Quotes": {
".read": true,
".write": false
}
}
}
What i am asking for?
I want to be able either show a quote everyday, and if that not possible to randomly show a quote. If i continue to do the randomly give a quote, what can i do to make sure its not overloading the app and crashing it. I want to have a lot of quotes rather than delete and add new ones every so often.

1."It takes so long for the app to load"
do you have databaseRef.keepSynced(true);?
this will load all database, so will takes very long
You should only read what you want
or you can FirebaseDatabase.getInstance().setPersistenceEnabled(Base.dataPersistence);
it will retain data without repeating read long time
2.You should check your database,If the format is wrong then it will die
3.I have the easiest way, you just record size at your firebase datebase Root directory,
you just read size,and produce random int to get quote,Or make a random number formula first,can easily specify quote,no need to download all.

Related

Preventing firebase Out of memory exception using Realtime database

I am trying to get data of a specific user when they log in, currently this is working well using the .child(userId) & .get() methods for a small amount of data/records. However, I created over 50,000+ test users & it ended up crashing on the android app.
My simple code for retrieving user data:
realtimeDatabase.getReference("users")
.child(userId)
.get()
.addOnCompleteListener(task -> {
if (task.isSuccessful()) {
DataSnapshot result = task.getResult();
if (result != null && result.exists()) {
Map<String, Object> mainMap = (Map<String, Object>) result.getValue();
//I should equate the result to a POJO instead of a Map, but I'll sort it out later :)
UserList userList = new UserList();
userList.setPoints((Long) mainMap.get("points"));
if (userList != null) {
long currentNoOfPoints = userList.getPoints();
...
}
}
}
});
I've checked out similar posts on SO & the common solution is using .limitToFirst(...) & equivalent methods... however in my case, I'm not showing the data on a list but simply want to retrieve the data for the user. In addition, correct me if I'm wrong getting the data using the .child(...) method is the same as creating a index since the whole node isn't retrieved.
The error trace I'm getting is:
E/RunLoop: Firebase Database encountered an OutOfMemoryError. You may need to reduce the amount of data you are syncing to the client (e.g. by using queries or syncing a deeper path). See https://firebase.google.com/docs/database/ios/structure-data#best_practices_for_data_structure and https://firebase.google.com/docs/database/android/retrieve-data#filtering_data
java.lang.OutOfMemoryError: Failed to allocate a 524304 byte allocation with 434072 free bytes and 423KB until OOM, target footprint 201326592, growth limit 201326592
at java.util.HashMap.resize(HashMap.java:703)
at java.util.HashMap.putVal(HashMap.java:628)
at java.util.HashMap.put(HashMap.java:611)
How can I solve this issue?
Edit
The exported 'users' node data looks like this
"users": {
"-N8sU8jQ0zE9klo8ukKC": {
"account_status": "active",
"displayName": "w9R5uBiYoQ",
"points": 39188,
"profilePic": "-"
},
"-N8sU8j__vlLa_UCCgvc": {
"account_status": "active",
"displayName": "LqO7LE9P95",
"points": 35743,
"profilePic": "-"
},
...

Filter Firebase Realtime Database data based on authentication

I'm using Firebase Realtime Database as the datastore for an Android app, and right now I'm trying to implement some basic filtering based on user permissions.
Here's my database:
{
"admins" : {
`user1hash` : true
},
"clients" : {
"client1hash" : {
"owner": "user1hash",
"name" : "Client 1"
},
"client2hash" : {
"owner": "user1hash",
"name" : "Client 2"
},
"client3hash" : {
"owner": "user2hash",
"name" : "Client 3"
}
}
}
I followed the examples in the Query-based Rules section here https://firebase.google.com/docs/database/security/securing-data and defined my rules like this:
{
"rules": {
"clients": {
".indexOn": "owner",
".read": "auth.uid != null && (root.child('admins/' + auth.uid).val() == true || query.orderByChild == 'owner' && query.equalTo == auth.uid)",
".write": "auth.uid != null",
".validate": "newData.hasChildren(['owner']) && newData.child('owner').val() == auth.uid"
}
}
}
And this is my client code (Android):
FirebaseDatabase database = FirebaseDatabase.getInstance();
String authUser = FirebaseAuth.getInstance().getCurrentUser().getUid();
DatabaseReference clientsDatabase = database.getReference("clients").orderByChild("owner").equalTo(authUser).getRef();
clientsDatabase.addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
// handle success
}
#Override
public void onCancelled(DatabaseError databaseError) {
// handle error
}
});
So basically I just want to be able to fetch all clients, but filter the results according to the access rules of the current user. Access rules are pretty simple, an user can only see the clients where he is the owner, except for admin users who can see all clients. For example if this was run by user1 which is an admin he should see all three clients, but if this is run by user2 which is a regular user he should only see client 3.
This implementation is working for admins, but I get a permissions error for regular users.
I tried the rules simulator in Firebase console, but it doesn't provide any documentation on how to use queries. Anyway I tried adding the query as a regular URL query string like /clients?orderByChild=owner&equalTo=user2hash, but this returns an empty error on the simulator with no description of the cause. The error I'm getting on the Android side doesn't have any description either, just a generic permissions error.
The problem is in this statement:
DatabaseReference clientsDatabase =
database.getReference("clients")
.orderByChild("owner")
.equalTo(authUser)
.getRef();
Specifically that last line getRef(), which throws everything away that you've done to build the query. The above statement leaves clientsDatabase exactly the same as:
DatabaseReference clientsDatabase = database.getReference("clients");
Which explains quite well why the statement fails.
You need to keep the Query that you get back and attach your listener on that:
DatabaseReference clientsDatabase = database.getReference("clients");
Query query = clientsDatabase.orderByChild("owner").equalTo(authUser);
query.addListenerForSingleValueEvent(new ValueEventListener() { ...

How to set firebase read rule and read all siblings if one matches

I have set my firebase rules as follows:
{
"rules": {"users": {
"$uid": {
".read": "auth != null",
".write": "$uid === auth.uid"
}}
}
}
Which allows write only to the node with matching uid and read all child nodes of every uid. But I want it to be like if I query using a child node under uid, only the matching child and it's siblings can be read...
for example this is my json structure:
{
"users" : {
"AJkK4yZJsoseeefrJ7i6KIOUBDghtrhgthrtDi1" : {
"lat" : 20.5001,
"long" : 68.3755,
"number" : "9876543210",
"time" : 1499599788090
}
}
}
I want to query using the number, and set the read rule as can read lat long and time only where the number matches. How to write the rule?
update: my question now is, how to query the database using number and get other siblings if value of number matches in android? I have tried this but not working:}
friend = mDatabase.getReference("users");
friend.keepSynced(true);
Query z = LocationActivity.this.friend.orderByChild("number").equalTo("9876054321");
z.addListenerForSingleValueEvent((new ValueEventListener() {
long lastseen;
public void onDataChange(DataSnapshot dataSnapshot) {
try {
for (DataSnapshot zoneSnapshot: dataSnapshot.getChildren()) {
lastseen = (Long)zoneSnapshot.child("time").getValue();
friendLatitude = (Double) zoneSnapshot.child("lat").getValue();
friendLongitude = (Double) zoneSnapshot.child("long").getValue();
}
} catch (Exception e) {
}}
it returns value null, any help would be appreciated.
Firebase read permissions are enforced when you attach a listener. In order to query a node, you must have read permission on that node (as Bradley also explained). So in order to be able to query users, you must have read permission on /users. And since any user that has read permission to /users can also read any data under that, you cannot use security rules to filter what nodes a user has access to.
This is known as rules are not filters and is one of the common pitfalls for developers new to Firebase security model. I recommend that you read the documentation I linked already and some of the many questions/answer about the topic.
The simplest solution for your use-case seem to be to pull up the .read rule to users:
{
"rules": {
"users": {
".read": "auth != null",
"$uid": {
".write": "$uid === auth.uid"
}
}
}
}
}
In order to query every node, the user needs to have read permission for ALL nodes under the parent.
This is because security rules cannot be used to query data in Firebase.
Not exactly sure if this answers the question, but I found this post looking for the following solution with respect to Firebase Storage .. where I can control which sibling nodes are accessible:
match /uploadRoot/{userId}/{uploadCategory}/{allPaths=**} {
// allow read access for client upload/download
allow read, write:
if request.auth != null
&& request.auth.uid == userId
&& (
uploadCategory == 'userTextFiles'
|| uploadCategory == 'userImages'
);

Firebase database security rule to access only user generated data with Android

I want to create a chat support app model. I have only 1 agent and many requesting users asking queries. Each user can have multiple conversations with the agent (assume it like a whatsapp group having only 2 members, a user and that agent).
So I create a data structure like:
Conversation
-uId
-status
-conversationTitle
and for Messages,
Message
-conversationId
-message
-from //0 mean it from me to my friend and 1 for a message from my friend to me
-timestamp
Basic structure will be like:
Conversation1:{ "uId": "1", "status":0, "conversationTitle": "First conversation"},
Conversation2:{ "uId": "2", "status":0, "conversationTitle": "Second conversation"},
Conversation1:{ "uId": "1", "status":0, "conversationTitle": "Third conversation"}
and for messages:
Message1: {"conversationId": "Conversation1", "message": "Hello there 1!", "from":0, "timestamp":******** },
Message2: {"conversationId": "Conversation1", "message": "Hello there 2!", "from":0, "timestamp":******** },
Message3: {"conversationId": "Conversation2", "message": "Hello there 3!", "from":0, "timestamp":******** },
Message4: {"conversationId": "Conversation3", "message": "Hello there 4!", "from":0, "timestamp":******** },
As you can see Message 1 and 2 belong to Conversation 1 which in turn b belong to User 1 with uID 1.
I want all conversation that is happening with User 1 (uId: 1) and User 2 (uID: 2) must not be able to see conversations of 1.
I wrote security as:
"conversations":{
"$uid":{
".read": "data.child('uId').val() == auth.uid",
".write":"auth != null && newData.child('uId').val() == auth.uid"
}
}
and then in Android:
FirebaseDatabase database = FirebaseDatabase.getInstance();
Query conversationsRef = database.getReference("conversations");
FirebaseRecyclerAdapter<Conversation, ConversationHolder> binderData = new FirebaseRecyclerAdapter<Conversation, ConversationHolder>(Conversation.class, R.layout.item_butler_converastions, ConversationHolder.class, conversationsRef) {
#Override
public void populateViewHolder(ConversationHolder chatMessageViewHolder, Conversation chatMessage, int position) {
chatMessageViewHolder.setName(chatMessage.getTitle());
}
#Override
protected void onCancelled(DatabaseError error) {
super.onCancelled(error);
}
};
When I do this I get permission error. I want to retrieve all conversation belonging to User 1.
I also tried using:
Query conversationsRef = database.getReference("conversations").orderByChild("uId").equalTo(mAuth.getCurrentUser().getUid());
but didn't work. Thanks in advance.

Android Firebase Listview ---> Hashmap Fail

I am trying to retrieve the Restaurant Name data from Firebase and output them in individual lines on ListView. I created a sample of the data which only consists of numbers(strings).
Retrieving the data seems fine as I could output them line by line in console, but my Hashmap is saving everything into the same "node" or "field"
Can anyone help me understand what I did wrong?
#Override
public void onDataChange(DataSnapshot Snapshot) {
int x = 1;
//Do some stuff once
for (DataSnapshot RestNames : Snapshot.getChildren()) {
name = RestNames.getValue().toString();
//System.out.println(name);
map.put(x, name);
x=x+1;
}
System.out.println(map);
Items.add(map);
System.out.println(Items);
listView.setAdapter(mgadapter);
}
The Output in the console is as follows :
{8=3456, 11=9, 9=34567, 5=3, 3=3, 4=4, 10=0, 1=1, 7=345, 6=34, 2=2}
Android emulator shows the same value for every single row.
I want to display each value on a separate row.
Thank you!
EDIT: SNIPPET OF JSON
{
"Eat": {
"Name": {
"-Jy3yehAkgqhg4knlxx_": "1",
"-Jy3yjQT2AxtZMqD2kov": "2",
"-Jy3yk96Mo5MKOEEzviJ": "3",
"-Jy3yksamL08R0BckxNZ": "4",
"-Jy5JBJYZUTxZQtmdDmi": "3",
"-Jy5JIXT_lDZrUOkF3T1": "34",
"-Jy5JJ0oMqGrs2vfFge2": "345",
"-Jy5JJTyET830PYOT3yA": "3456",
"-Jy5JJu-jDGMDXncWDKf": "34567",
"-Jy5JVejdsUtggM8vBoi": "0",
"-Jy5JbwEoWrKAi6XIVQY": "9"
}
}
}
Since we're missing some code in your snippet, I completed it an ran it locally:
Firebase ref = new Firebase("https://stackoverflow.firebaseio.com/32367022/Eat/Name");
ref.addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot snapshot) {
int x = 1;
Map<Integer,Object> map = new HashMap<Integer, Object>();
for (DataSnapshot child: snapshot.getChildren()) {
String name = child.getValue().toString();
System.out.println(name);
map.put(x, name);
x=x+1;
}
System.out.println(map);
}
#Override
public void onCancelled(FirebaseError firebaseError) {
}
});
This prints out the following for me:
1
2
3
4
3
34
345
3456
34567
0
9
{1=1, 2=2, 3=3, 4=4, 5=3, 6=34, 7=345, 8=3456, 9=34567, 10=0, 11=9}
The first lines show the output from inside the for loop. The last lines shows the HashMap.
I use the most basic of Java classes, and this behavior seems correct to me.
I expect that the System.out.println(name); inside the for loop will display the same output as above for you, because the Firebase SDK handles ordering there.
If the order in the loop is correct, either your map or your Items object changes the order. It is impossible to say without seeing the types of these.
But in general, the approach above is how you should troubleshoot this problem: isolate it to a single object/class and then either fix what's wrong with that class (if it's your code) or replace it with a class that works for your use-case (if it comes from a library).
Update: I created a minimal app that shows the numbers in the correct order in a list view in this Github repo.

Categories

Resources