How to get nested push key from Firebase database Android - java

I have my DB structure like this:
When someone follows someone else or likes a picture or something, the notification goes into the Notifications table followed by the users id(who sent the notif) followed by a unique id to separate the notifications. If I don't add that unique id(push()) only one notification is added and when a new one is supposed to be added only the time and text of the notification changes.
This is how I add the notification:
private void addNotifications(String userid, String postid) {
DatabaseReference reference = FirebaseDatabase.getInstance().getReference("Notifications").child(userid);
String notificationid = reference.getKey();
mGroupId = reference.push().getKey();
HashMap<String, Object> hashMap = new HashMap<>();
hashMap.put("dateAdded", System.currentTimeMillis());
hashMap.put("date_time", getTimeDate());
hashMap.put("notificationid", notificationid);
hashMap.put("userid", firebaseUser.getUid());
hashMap.put("text", "liked your post");
hashMap.put("postid", postid);
hashMap.put("ispost", true);
reference.child(mGroupId).setValue(hashMap);
Log.d("Post Key", mGroupId);
}
This shows me in the logcat the correct push key that was just added Log.d("Post Key", mGroupId);.
I am trying to delete anything older than a day in my notifications table. When I try to access mGroupId in the notifications fragment I get this error java.lang.NullPointerException: Can't pass null for argument 'pathString' in child().
I have been checking answers and comments for days now from experts like Frank Van P. and Alex Mamo and from regular people. I tried downvoted answers and comments and still nothing. I asked this before but the question keeps getting closed as a duplicate even though none of the duplicates help. How can I proceed?
How I try to delete the notifications older than a day:
private void lessThanADayNotification() {
DatabaseReference reference = FirebaseDatabase.getInstance().getReference("Notifications").child(firebaseUser.getUid());
DatabaseReference freezerItemsRef = reference.child(PostAdapter.mGroupId);
//Log.i("uid", uid);
long cutoff = new Date().getTime() - TimeUnit.MILLISECONDS.convert(12, TimeUnit.SECONDS);
Query oldBug = reference.child("Notifications").orderByChild("dateAdded").endAt(cutoff);
oldBug.addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot snapshot) {
Notification notification = new Notification();
freezerItemsRef.child(notification.getNotificationid()).removeValue();
}
#Override
public void onCancelled(DatabaseError databaseError) {
}
});
}

I've said this before on the question that you since deleted: there is no way to query on your dateAdded property from the Notifications node.
Firebase queries operate on a flat list of the child nodes directly on the node where you query. So in your data model you can query notifications for a specific user, but you can't query notifications across all users.
If you want to query across the notifications of all users, you will need to store them as a flat list, with the UID in that case stored as a property of each notification node.
See:
Firebase Query Double Nested
Firebase query if child of child contains a value

Related

Data is not written into the Firebase Database from Android

I have set up a Firebase database that has 2 nodes. One node is called "ingredients" and the other is called "orders". I set up the nodes manually by using a JSON file and I just entered 1 entry in the node "orders" manually. You can see a screenshot of the structure of the database:
Now I would like to add further entries in the table "orders" (so basically I would like to add a further row).
In my Android application I use the following code:
// in the class definition of the Fragment
public DatabaseReference firebase_DB;
...
// in the oneCreate Method of the Fragment
firebase_DB = FirebaseDatabase.getInstance().getReference("orders");
...
// when clicking on a button in the Fragment which calls the onClick method
String id = firebase_DB.push().getKey();
DB_Object_Test currentOrder= new DB_Object_Test("testInfo_2", "testName", 2);
firebase_DB.child(id).setValue(currentOrder);
So basically the code runs without an error and the firebase_DB.child(id).setValue(currentOrder); is called. However, nothing changes in the Firebase console. I can't see any new entry there. The firebase database is properly connected to the app (at least Android Studio says that) and I have not specified any rules that would prohibit writing something on the database. The same data is stored without any problems in an SQLite database.
Can any one of you think about a possible reason for this problem? Why is the new row not written into the firebase database? I'll appreciate every comment.
EDIT: Here is the adjusted code that writes to the firebase database (by using 3 different approaches) after clicking a "Order" Button in the Fragment.
public void onClick(View view) {
if(view.getId() == R.id.ordering_button) {
/* Firebase approach 1
final FirebaseDatabase database = FirebaseDatabase.getInstance();
DatabaseReference ref = database.getReference("");
DatabaseReference usersRef = ref.child("orders");
Map<String, DB_Object_Test> order = new HashMap<>();
order.put("order_2", new DB_Object_Test("1", "testInfo_2", "testName", "2"));
usersRef.setValue(order);
*/
/*
Firebase approach 3 (by Alex)
*/
DatabaseReference rootRef = FirebaseDatabase.getInstance().getReference();
DatabaseReference ordersRef = rootRef.child("orders");
Map<String, Object> order = new HashMap<>();
order.put("info", "No additional information");
order.put("name", "Another Test Order");
order.put("table", "15");
Log.e("LogTag", "Before write firebase");
ordersRef.child("order_2").setValue(order).addOnCompleteListener(new OnCompleteListener<Void>() {
#Override
public void onComplete(#NonNull Task<Void> task) {
Log.e("LogTag", "During on Complete firebase");
if (task.isSuccessful()) {
Log.e("dbTAG", "Data successfully written.");
} else {
Log.e("dbTAG", task.getException().getMessage());
}
}
});
Log.e("LogTag", "Afer write firebase");
/* Firebase approach 2
DatabaseReference firebase_DB = FirebaseDatabase.getInstance().getReference("orders");
String id = firebase_DB.push().getKey();
DB_Object_Test currentOrder= new DB_Object_Test(id, "testInfo_2", "testName", "2");
firebase_DB.child(id).setValue(currentOrder);
//Option 2*
//firebase_DB.setValue(currentOrder);
Log.e("LogTag", "firebase_DB.child(id): " + firebase_DB.child(id));
*/
Navigation.findNavController(getView()).navigate(...);
}
}
To be able to add another object under your "orders" node, please use the following lines of code:
DatabaseReference rootRef = FirebaseDatabase.getInstance().getReference();
DatabaseReference ordersRef = rootRef.child("orders");
Map<String, Object> order = new HashMap<>();
order.put("info", "No additional information");
order.put("name", "Another Test Order");
order.put("table", "15");
ordersRef.child("order_2").setValue(order).addOnCompleteListener(new OnCompleteListener<Void>() {
#Override
public void onComplete(#NonNull Task<Void> task) {
if (task.isSuccessful()) {
Log.d("TAG", "Data successfully written.");
} else {
Log.d("TAG", task.getException().getMessage());
}
}
});
This code will work only if you set the proper security rules in your Firebase console:
{
"rules": {
".read": true,
".write": true
}
}
Remember to keep these rules only for testing purposes. Besides that, you can also attach a complete listener to see if something goes wrong.
If you want to use DatabaseReference#push() method, then you should use the following line of code:
ordersRef.push().setValue(order);
Edit:
According to your last comment:
Database location: Belgium (europe-west1)
You should check my answer from the following post and add the correct URL to the getInstance() method:
getInstance() doesn't work with other location than us-central1 in Realtime Database
In your particular case it should be:
DatabaseReference rootRef = FirebaseDatabase.getInstance("https://drink-server-db-default-rtdb.europe-west1.firebasedatabase.app").getReference();

Firebase authentication - Display user info

I use the firebase realtime database to store user information.
Here is a picture of the database:
screenshot of the database
For example, how can I get the value "nombre_dons" according to the connected user to display it in the application ? I use android studio in java.
Thank you in advance for your help
Those parent strings of the user data in Users node in your database look like the uids. If they indeed are, then all you need to do is to retrieve data from those uids for the particular user.
What I am saying looks something like this in code:
DatabaseReference ref = FirebaseDatabase.getInstance().getReference().child("Users");
FirebaseAuth mAuth = FirebaseAuth.getInstance();
ref.child(mAuth.getUid()).addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
String data = dataSnapshot.child("nombre_dons").getValue(String.class);
}
#Override
public void onCancelled(DatabaseError databaseError) {
Log.d(TAG, "onCancelled", databaseError.toException());
}
});
One flaw I can see in your database image is that nombre_dons child in second user in your Users node has capital N, which would be a problem, because Firebase is case sensitive. I'd advise you to keep it same as others.
Assuming the keys inside 'Users' in your database are Firebase Authentication UserId(s), you can follow these steps-
1. Get UID of the current user.
2. Fetch data for the same.
Using Javascript -
var uid = firebase.auth().currentUser.uid;
firebase.database().ref('/Users/' + uid).once('value').then(function(snapshot){
var nombre_dons = snapshot.val().nombre_dons;
});

How to exclude an element from a Firestore query?

I have a collection of users and I want to query all users from the database and display them in a RecyclerView except one, mine. This is my db schema:
users [collection]
- uid [document]
- uid: "fR5bih7SysccRu2Gu9990TeSSyg2"
- username: "John"
- age: 22
- //other users
How to query the database like so:
String uid = FirebaseAuth.getInstance().getCurrentUser().getUid();
Query q = db.collection("users").whereNotEqualTo("uid", uid);
So I need this query object to be passed to a FirestoreRecyclerOptions object in order to display all the other users in RecyclerView.
Is this even possible? If not, how can I solve this? Thanks!
Edit:
options = new FirestoreRecyclerOptions.Builder<UserModel>()
.setQuery(query, new SnapshotParser<UserModel>() {
#NonNull
#Override
public UserModel parseSnapshot(#NonNull DocumentSnapshot snapshot) {
UserModel userModel = documentSnapshot.toObject(UserModel.class);
if (!userModel.getUid().equals(uid)) {
return userModel;
} else {
return new UserModel();
}
}
}).build();
After days and days of struggling with this issue, I finally found the answer. I could not solve this without the help of #Raj. Thank you so much #Raj for the patience and guidance.
First off all, according to the answer provided by #Frank van Puffelen in his answer from this post, I stopped searching for a solution that can help me pass two queries to a single adapter.
In this question, all that I wanted to achieve was to query the database to get all the users except one, me. So because we cannot combine two queries into a single instance, I found that we can combine the result of both queries. So I have created two queries:
FirebaseFirestore db = FirebaseFirestore.getInstance();
Query firstQuery = db.collection("users").whereLessThan("uid", uid);
Query secondQuery = db.collection("users").whereGreaterThan("uid", uid);
I'm having a UserModel (POJO) class for my user object. I found not one, but two ways to solve the problem. The first one would be to query the database to get all user objects that correspond to the first criteria and add them to a list. After that, query the database again and get the other user objects that correspond to the second criteria and add them to the same list. Now I have a list that contains all the users that I need but one, the one with that particular id from the queries. This is the code for future visitors:
firstQuery.get().addOnCompleteListener(new OnCompleteListener<QuerySnapshot>() {
#Override
public void onComplete(#NonNull Task<QuerySnapshot> task) {
List<UserModel> list = new ArrayList<>();
if (task.isSuccessful()) {
for (DocumentSnapshot document : task.getResult()) {
UserModel userModel = document.toObject(UserModel.class);
list.add(userModel);
}
secondQuery.get().addOnCompleteListener(new OnCompleteListener<QuerySnapshot>() {
#Override
public void onComplete(#NonNull Task<QuerySnapshot> task) {
if (task.isSuccessful()) {
for (DocumentSnapshot document : task.getResult()) {
UserModel userModel = document.toObject(UserModel.class);
list.add(userModel);
}
//Use the list of users
}
}
});
}
}
});
The second approach would be much shorter because I use Tasks.whenAllSuccess() like this:
Task firstTask = firstQuery.get();
Task secondTask = secondQuery.get();
Task combinedTask = Tasks.whenAllSuccess(firstTask, secondTask).addOnSuccessListener(new OnSuccessListener<List<Object>>() {
#Override
public void onSuccess(List<Object> list) {
//This is the list that I wanted
}
});
According to the official firestore documentation:-
Cloud Firestore does not support the following type of query:
Queries with a != clause. In this case, you should split the query
into a greater-than query and a less-than query. For example, although
the query clause where("age", "!=", "30") is not supported, you can
get the same result set by combining two queries, one with the clause
where("age", "<", "30") and one with the clause where("age", ">", 30).
If you are using FirestoreRecyclerAdapter then FirestoreRecyclerOptions will directly accepts the query using setQuery() method and hence not allows you to perform client side filtering.
If you try to apply filters in onBindViewHolder() while setting the data that might results in empty items in the recycler view. In order to resolve that refer Method 2.
So, the possible solution to your problem would be to create an integer field in your users collection under every document. Eg:-
users [collection]
- uid [document]
- uid: "fR5bih7SysccRu2Gu9990TeSSyg2"
- username: "John"
- age: 22
- check: 100
In this I have created a 'check' variable whose value is 100. So, put value of 'check' in all other documents as less than 100.
Now, you can easily make a query that finds documents with check<100 as:-
Query q = db.collection("users").whereLessThan("check", 100);
This will retrieve all your documents except the one you don't want. And while setting the data you can set other parameters skipping the check variable.
Method 2 (Client Side Filtering)
We can apply a check in onBindViewHolder() method that if the retrieved uid matches with current user uid then set the height of Recycler view as 0dp. As:-
ViewUserAdapter.java
public class ViewUserAdapter extends FirestoreRecyclerAdapter<User, ViewUserAdapter.ViewUserHolder>
{
String uid;
FirebaseAuth auth;
public ViewUserAdapter(#NonNull FirestoreRecyclerOptions<User> options)
{
super(options);
auth = FirebaseAuth.getInstance();
uid = auth.getCurrentUser().getUid();
}
#Override
protected void onBindViewHolder(#NonNull ViewUserHolder holder, int position, #NonNull User model)
{
DocumentSnapshot snapshot = getSnapshots().getSnapshot(position);
String id = snapshot.getId();
if(uid.equals(id))
{
RecyclerView.LayoutParams param = (RecyclerView.LayoutParams)holder.itemView.getLayoutParams();
param.height = 0;
param.width = LinearLayout.LayoutParams.MATCH_PARENT;
holder.itemView.setVisibility(View.VISIBLE);
}
else
{
holder.tvName.setText(model.name);
holder.tvEmail.setText(model.email);
holder.tvAge.setText(String.valueOf(model.age));
}
}
}
2021 Update: This Is Supported
Howdy devs. It looks like this is now supported with the where operator used like this: citiesRef.where("capital", "!=", false);
Firestore doesn't support not equal to operation. So you need to filter the data at the client side. Since in you case you only have one extra item you can filter it out.
For that you may need to build your own recycler implementation where when adding data to recycler adapter data layer, you restrict the data when ever it matches your != condition.
I haven't explored recycler implementation firebase provided so I cannot say it supports data manipulation to adapter data or not.
Here is a good resource to start implementing recycler view : https://www.androidhive.info/2016/01/android-working-with-recycler-view/
The simplest solution would be to use a PagedListAdapter and create a custom DataSource for the Firestore queries. In the DataSource the Query can be transformed into an Array or ArrayList in which you can easily remove your item before adding the data to the method callback.onResult(...).
I used a similar solution to process data after a Firestore query in order to filter and sort by a time attribute, and then re-sort by a quality score attribute in the client before passing the data back in to callback.onResult(...).
Documentation
Google: Build your own data sources
Codepath: Paging Library Guide
Data Source Sample
class ContentFeedDataSource() : ItemKeyedDataSource<Date, Content>() {
override fun loadBefore(params: LoadParams<Date>, callback: LoadCallback<Content>) {}
override fun getKey(item: Content) = item.timestamp
override fun loadInitial(params: LoadInitialParams<Date>, callback: LoadInitialCallback<Content>) {
FirestoreCollections.contentCollection
.collection(FirestoreCollections.ALL_COLLECTION)
.orderBy(Constants.TIMESTAMP, Query.Direction.DESCENDING)
.whereGreaterThanOrEqualTo(Constants.TIMESTAMP, DateAndTime.getTimeframe(WEEK))
.limit(params.requestedLoadSize.toLong())
.get().addOnCompleteListener {
val items = arrayListOf<Content?>()
for (document in it.result.documents) {
val content = document.toObject(Content::class.java)
items.add(content)
}
callback.onResult(items.sortedByDescending { it?.qualityScore })
}
}
override fun loadAfter(params: LoadParams<Date>, callback: LoadCallback<Content>) {
FirestoreCollections.contentCollection
.collection(FirestoreCollections.ALL_COLLECTION)
.orderBy(Constants.TIMESTAMP, Query.Direction.DESCENDING)
.startAt(params.key)
.whereGreaterThanOrEqualTo(Constants.TIMESTAMP, DateAndTime.getTimeframe(WEEK))
.limit(params.requestedLoadSize.toLong())
.get().addOnCompleteListener {
val items = arrayListOf<Content?>()
for (document in it.result.documents) {
val content = document.toObject(Content::class.java)
items.add(content)
}
val sortedByQualityScore = ArrayList(items.sortedByDescending { it?.qualityScore })
callback.onResult(sortedByQualityScore)
sortedByQualityScore.clear()
}
}
}
Simpler and earlier client-side filtering (when you add items to your list):
Get the current user's ID by using Firestore's standard method.
Get the name of the doc for all the users in your user collection.
Before adding the user to
your RecyclerView list, check that the user it is about to add to your list is not the current user.
When done is this way, you can use the "not equals" method on the client side and not get into any Firestore issues. Another benefit is that you don't have to mess with your adapter or hide the view from a list-item you didn't want in the recycler.
public void getUsers(final ArrayList<Users> usersArrayList, final Adapter adapter) {
CollectionReference usersCollectionRef = db.collection("users");
Query query = usersCollectionRef
.whereEqualTo("is_onboarded", true);
query.get()
.addOnCompleteListener(new OnCompleteListener<QuerySnapshot>() {
#Override
public void onComplete(#NonNull Task<QuerySnapshot> task) {
if (task.isSuccessful()) {
for (QueryDocumentSnapshot document : task.getResult()) {
final String otherUserID = document.getId();
FirebaseUser user = mAuth.getCurrentUser();
String currentUserID = user.getUid();
if (!otherUserID.equals(currentUserId)) {
usersArrayList.add(new User(otherUserID));
adapter.notifyDataSetChanged(); //Ensures users are visible immediately
}
} else {
Log.d(TAG, "get failed with ", task.getException());
}
}
});
}
}
} else {
Log.d(TAG, "Error getting documents: ", task.getException());
}
}
});
}
You don't have to do all this
Just do normal query and hide the layout by setting getLayoutParams().height and width to 0 respectively. See example below.
if(firebaseUserId.equalIgnoreCase("your_holder_firebase_user_id"){
holder.mainLayout.setVisibility(View.INVISIBLE);
holder.mainLayout.getLayoutParams().height = 0;
holder.mainLayout.getLayoutParams().width = 0;
}else {
//show your list as normal
}
//This will hide any document snapshot u don't need, it will be there but hidden
here's my solution with flutter for usernames
Future<bool> checkIfUsernameExistsExcludingCurrentUid(
// TODO NOT DONE
String username,
String uid) async {
print("searching db for: $username EXCLUDING SELF");
bool exists = true;
QuerySnapshot result = await _firestore
.collection(USERS_COLLECTION)
.where(
"username",
isEqualTo: username,
)
.getDocuments();
List _documents = result.documents;
_documents.forEach((element) {
if (element['uid'] == uid) {
exists = false;
} else {
return true;
}
});
return exists;
}

Firebase Multi Location Updates - Setting Children With Empty Values

I am trying to use multi-location updates in order to change values in the Realtime Database and have one listener to follow all of the changes at once.
This is what I have tried to do:
DatabaseReference mDatabase = FirebaseDatabase.getInstance().getReference();
Map data = new HashMap();
data.put("Users/"+senderEmail+"/friends_requests_sent/"+receiverEmail,"");
data.put("Users/"+receiverEmail+"/friends_requests_received/"+senderEmail,"");
mDatabase.updateChildren(data, new DatabaseReference.CompletionListener() {
#Override
public void onComplete(DatabaseError databaseError, DatabaseReference databaseReference) {
if (databaseError!=null)
showConnectionErrorToast();
else{
Context context=getContext();
Toast.makeText(context,
context.getString(R.string.friend_request_was_sent), Toast.LENGTH_SHORT).show();
}
}
});
The children are being added just fine (receiverEmail and senderEmail), but it forces me to put values in them, although I don't want to. I want that the email will be the key of this children, without a value.
Is this possible in this way?
If it's not, what will be the best alternative?
There is no way to store a key without a value in the Firebase Database. In cases where you really don't have a value to store, consider using a marker value like true or "TBD".

How to fetch data on Firebase from time to time?

I am fetching data on Firebase from time to time because I am tracking someone's GPS. That someone is saving his location in an interval of 5 minutes so that I can track his GPS. But my problem is how do I fetch data from Firebase with an interval of 5 minutes too?
Or is there any possible way other than this?
Thanks in advance. :)
So if someone is updating his/her location in every five minutes, then you really don't have to run a CounterDownTimer in your side to track the location in every five minutes. So I think you just need to add a listener to that node at Firebase that you want to track.
So here's a simple implementation for you. Copied from Firebase Tutorial
Firebase ref = new Firebase("https://docs-examples.firebaseio.com/web/saving-data/fireblog/posts");
// Attach an listener to read the data at our posts reference
ref.addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot snapshot) {
System.out.println(snapshot.getValue());
}
#Override
public void onCancelled(FirebaseError firebaseError) {
System.out.println("The read failed: " + firebaseError.getMessage());
}
});
I'm copying quotes from there too. So that you know it serves your purpose.
This method will be called anytime new data is added to our Firebase
reference, and we don't need to write any extra code to make this
happen.
So each time the person you want to track will update his/her location, you'll get a callback in the method stated above and will take necessary action. You really don't have to implement a polling mechanism to do the tracking. That's the way Firebase works actually.
No Needs to put any kind of service of Scheduler to retrieve data from firebase.
As Firebase provide realtime database .. whenever you push your data on Firebase Database the listener will trigger and you can retrieve your data..
Implement following Listener, using this you can retrieve your data whenever database get update.
DatabaseReference mDatabase =FirebaseDatabase.getInstance().getReference();
ValueEventListener yourModelListener = new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
// Get YOURMODEL object and use the values to update the UI
YourModel mModel = dataSnapshot.getValue(YourModel.class);
Log.e("Data : ",""+mModel);
}
#Override
public void onCancelled(DatabaseError databaseError) {
// Getting Post failed, log a message
Log.w(TAG, "loadPost:onCancelled", databaseError.toException());
}
};
mDatabase.addValueEventListener(yourModelListener);
For More Info about Listeners .. https://firebase.google.com/docs/database/android/retrieve-data

Categories

Resources