I'm trying to write messenger app using Firebase.
In database I have a few entries, which are User.class objects. I'm trying to write function which can download User object from database. I though that it'd be better to build separate class (UserManager) for this task, because I don't like making mess in code. But there is a problem, because in onCreate method I need to use User object to download some additional info from database to create conversation list, so downloading user from server should be done before that. Also if user is not in database, it should create and push User to database using FirebaseAuth (I've got that working).
Should I build class extending AsynchTask, and there put downloading user, and then updating UI with the data downloaded after user ?
How do I know if the user was already downloaded. Probably I should build some listener but I don't know how to do that.
Additional question:
If I use this reference with value listener, do i get a user object or some value from inside of the object?
DatabaseReference userReference = FirebaseDatabase.getInstance().getReference().child("users/" + mUserID);
Here is my database:
Each entry key is userID from FirebaseAuth for easier implementation.
I've been cracking my head on this for a few days and tried different approaches. I'll apriciate any help. I think, that some code or a scheme would be a huge help.
How do I know if the user was already downloaded?
You can add a flag to each user with the value of false and once you have downloaded the user object, to set the value to true but this is not how things are working with Firebase. You cannot know when a user from the database is completed downloaded becase Firebase is a realtime database and getting data might never complete. That's why is named a realtime database because in any momemnt the data under that User object can be changed, properties can be added or deleted.
You can use a CompletionListener only when you write or update data and you'll be notified when the operation has been acknowledged by the Database servers but you cannot use this interface when reading data.
If I use this reference with value listener, do i get a user object or some value from inside of the object?
If the value that you are listening to is a User object, then you'll get a User object. If the value is another type of object, which can also be a String (which is also an object) then you'll get that type of object, which can also be a String object. Remember, that only the keys in a Firebase database are always strings.
Maybe this part of my code will help you figure out:
DatabaseReference ref = FirebaseDatabase.getInstance().getReference().child("users")
.child(mUserID);
ref.addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
User user = dataSnapshot.getValue(User.class);
}
#Override
public void onCancelled(DatabaseError databaseError) {
Log.e(TAG, "DatabaseError: " + databaseError.getMessage());
}
});
Related
This is what my Firestore database looks like:
I have a RecyclerView which displays a list of posts. But I am not able to retrieve a document with its subcollections and put it inside an object. When I submit a list of posts in my RecyclerView, I want each post to already have its Likes and Comments.
I use AsyncTask to do the background work of querying all the posts of the user. After the AsyncTask is done, it will submit the List of posts to the RecyclerView. (Still doesn't work)
In the code below, I'm calling all the Posts of the user and then getting the "Likes" of each post.
I have put a while loop inside each post so that I can wait for it to finish querying the Likes before proceeding to the next Post snapshot
doInBackground()
final List<Post> postList = new ArrayList<>();
// -- GET all the posts of the user
feedsCollection.document(documentID).collection("Posts").get()
.addOnSuccessListener(new OnSuccessListener<QuerySnapshot>() {
#Override
public void onSuccess(QuerySnapshot queryDocumentSnapshots) {
for(final DocumentSnapshot documentSnapshot1 : queryDocumentSnapshots){
final Post post = documentSnapshot1.toObject(Post.class);
// Finish getting the likes&comments before proceeding
while(post.isStillLoading()){
// -- GET Likes of each post
db.collection("FeedsCollection").document(documentID).collection("Posts")
.document(documentSnapshot1.getId()).collection("Likes").get()
.addOnSuccessListener(new OnSuccessListener<QuerySnapshot>() {
#Override
public void onSuccess(QuerySnapshot queryDocumentSnapshots) {
List<Like> likeList = new ArrayList<>();
for(DocumentSnapshot likeSnapshot : queryDocumentSnapshots){
Like like = likeSnapshot.toObject(Like.class);
likeList.add(like);
}
post.setLikes(likeList);
post.setStillLoading(false);
}
});
// -- GET comments code
}
postList.add(post);
}
}
});
return postList;
onPostExecute()
protected void onPostExecute(List<Post> postList) {
super.onPostExecute(postList);
adapter.submitData(postList);
}
These are the things I've tried:
I tried having multiple
viewtypes in my recyclerview, so that if the list is still empty, it
displays "No items". And then when the AsyncTask finish, it will
re-submit a list to the recyclerview.
I tried to use FirestoreRecyclerAdapter but I've read that its queries are shallow so it can't get the Likes and Comments subcollections of each post document altogether.
Any other workaround for this?
But I am not able to retrieve a document with its subcollections and put it inside an object.
There's no way you can achieve this. The queries in Firestore are shallow, so it is allowed to only get documents from the collection that the query is run against. Furthermore, there is no way you can get a document together with the nested subcollections in a single go. You can get the document and then run separate queries for each and every subcollection.
Another possible solution might be to use Firebase Realtime Database, where when you download a node, you download it together with all the data that exist beneath that node.
I use AsyncTask to do the background work of querying all the posts of the user.
The Cloud Firestore client already runs all network operations in a background thread. This means that all operations take place without blocking your main thread. Putting it in an AsyncTask does not give any additional benefits.
If you have data in multiple collections, even nested collections, you will have to perform multiple queries to get all that data. It won't be possible with just a single query. You can certainly implement what you want, it will just be significantly more complex to do so.
It will require one query to get all the documents in PostsCollection, then more queries for each of the subcollections nested under Comments and Likes for each of the posts.
Switching to Realtime Database as Alex suggests might not be a good idea. You will lose a lot of more complex querying capability just to get deep queries, and you might not even want fully deep queries all the time, as that could be more expensive overall.
Well, not exactly at the same time. More like one after the other, in a for-loop.
Problem: So in some instances, the code I used did manage to create multiple accounts but it often gives me the error that the user is null and fails to write to the database.
The code:
Database db = FirebaseDatabase.getInstance().getReference();
String password = "1234567"; // Same password for all accounts
ArrayList emailArray = new ArrayList(); // For example, 20 premade emails
for (int i = 0; i < emailArray.size(); i++){
mAuth.createUserWithEmailAndPassword((String) emailArray.get(i), password).addOnCompleteListener(new OnCompleteListener<AuthResult>() {
#Override
public void onComplete(#NonNull Task<AuthResult> task) {
if (mAuth.getCurrentUser() != null) {
String userId = mAuth.getCurrentUser().getUid(); // This value is returning null
// Writing a bunch of things to the database for each user
db.child(userId).child("name").setValue(...)
...
mAuth.signOut();
}
}
});
}
I think I may have to use an AuthStateListener but I am unsure and I don't know how to implement it. How would I go about accomplishing this? Please let me know if I should include anything else in my post. Thank you!
You should not use the client-side Android SDK to create multiple users like this. There's a significant chance that your app will be flagged for abuse, and locked out of the system.
For administrative use-cases, use the Admin SDK in a trusted environment.
On a code level, the problem occurs because there's only one value for mAuth.getCurrentUser() and you're trying to create all the users in parallel. You'll need to wait for each createUserWithEmailAndPassword to be completed, before starting on the next one. But as said above: this approach is flawed to begin with, so I don't recommend pursuing this path further.
I want to update a document with a User object that I have, but I do not want the document to be created if it does not exist, and therefore I cannot use "DocumentReference.set" with "SetOptions.Merge()" (to my understanding).
However, according to this post (Difference between set with {merge: true} and update), "update" is actually the command I need. My problem is, it doesn't seem like update accepts a Java object.
I do not want to check whether or not the document exists myself, as this will result in an unnecessary read.
Is there any way around this?
Here is my code (I have removed success and failure listeners for simplicity):
public void saveUser(User user)
{
CollectionReference collection = db.collection("users");
String id = user.getId();
if (id.equals(""))
{
collection.add(user);
}
else
{
// I need to ensure that the ID variable for my user corresponds
// with an existing ID, as I do not want a new ID to be generated by
// my Java code (all IDs should be generated by Firestore auto-ID)
collection.document(ID).set(user);
}
}
It sounds like you:
Want to update an existing document
Are unsure if it already exists
Are unwilling to read the document to see if it exists
If this is the case, simply call update() and let it fail if the document doesn't exist. It won't crash your app. Simply attach an error listener to the task it returns, and decide what you want to do if it fails.
However you will need to construct a Map of fields and values to update using the source object you have. There are no workarounds for that.
The question may sound weird. I have the following custom Object that I named ItemUser:
private UserInfo user_info;
private List<UserAchievement> user_achievements;
Both fields have getters and setters. My Firestore's database looks like this:
I would like to get the List size instead of re-calling the database and getting the size of the collection from a separated call that would consume much resources and take a lot of time (3-4s).
Firstly I'm getting the data using this:
mDB.collection("COLLECTION_NAME").document("USER_ID").get()
Inside the onCompletedListener I'm getting the custom object as the following:
ItemUser mUser = task.getResult().toObject(ItemUser.class);
Now, when I'm trying to get the size of the user_achievements, a NullPointerException popups saying I can't get the size of a null reference.
Therefore the user_achievements is null. I think the way I'm defining user_achievements in my custom Object is the reason for this exception.
The question is: How could this be possible done without recalling the database to count only the size?
I have the main custom Object ItemUser and its children are 'healthy' except user_achievements because of the way it's defined - List<UserAchievement>.
So, any suggestions to overpass this issue?
How could this be possible done without recalling the database to count only the size?
No, because Cloud Firestore is a real-time database and items can be added or deleted, so to get the size of a list you need to query the database and use a get() call.
If you want to count the number of documents beneath a collection (which can be added to a list), please see my answer from this post in which I have explained that task.getResult().size() can help you solve the problem.
Edit:
mDB.collection("COLLECTION_NAME").document("USER_ID").get().addOnCompleteListener(new OnCompleteListener<QuerySnapshot>() {
#Override
public void onComplete(#NonNull Task<QuerySnapshot> task) {
if (task.isSuccessful()) {
int size = task.getResult().size();
Log.d(TAG, String.valueOf(size));
}
}
});
I am trying to save location (so latitide and longitude) as one of the keys/fields in Firebase. In their example SFVehicles, they do show how to query once the information is stored but my problem is how do i save in the first place.
In their blog post, GeoFire goes Mobile, they are showing how the data would look like - but how do I get that location field populated?
I am able to save other types of strings to the Firebase though. I just use the code below.
Question is What data type should the location field be?
Firebase ref = new Firebase("https://myfirebaselink.firebaseio.com/");
//User
alan = new User("Alan Turing", 1912);
alanRef.setValue(obj);
I tried location to be a List<String>, but that did not work -- the location field looked like below:
Edit: On more research, found this blog post by Google but they are also saving as keys latitude1 and longitude. This probably was written before GeoFire` was introduced.
The GeoFire for Java project has a great README, that covers (amongst other) setting location data:
In GeoFire you can set and query locations by string keys. To set a location for a key simply call the setLocation() method. The method is passed a key as a string and the location as a GeoLocation object containing the location's latitude and longitude:
geoFire.setLocation("firebase-hq", new GeoLocation(37.7853889, -122.4056973));
To check if a write was successfully saved on the server, you can add a GeoFire.CompletionListener to the setLocation() call:
geoFire.setLocation("firebase-hq", new GeoLocation(37.7853889, -122.4056973), new GeoFire.CompletionListener() {
#Override
public void onComplete(String key, FirebaseError error) {
if (error != null) {
System.err.println("There was an error saving the location to GeoFire: " + error);
} else {
System.out.println("Location saved on server successfully!");
}
}
});
To remove a location and delete it from the database simply pass the location's key to removeLocation:
geoFire.removeLocation("firebase-hq");
It looks from here
That the type of the object is GeoLocation , like in line 83.