Firestore - Custom objects for nested collection - java

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));
}
}
});

Related

How to display a document with its subcollections from Firestore in a RecyclerView with Android?

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.

Firestore - Use update method for document but with Object (Java)

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.

Download user from Firebase, Messenger app

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());
}
});

MediatorLiveData.getValue().isEmpty() returns false after delete directly

How can I check if a table is empty knowing that I'm not in the Activity with a guaranteed or synchronized returned value??
Hey guys, I'm new to architecture components.
I having a very strange problem with MediatorLiveData..
all I need to do is to delete all values in a Room Entity using background service "AlarmManager"
and postValue(null) to the MediatorLiveData,
then I need to call refreshTables() which check if there is no data in the table it calls a webservice and inserts the values.
everything works fine but when I watched the calling of refreshTables() after the deleting, sometimes the condition getValue().isEmpty() returns false!!
when this thing happens it leaves the table without data.
I believe there is a delay in MediatorLiveData observing.
Notes:
1- I'm calling refreshTables() in a thread (executor) after the deleting, of course, refreshTables() executes using its executor.
2- this fault happens almost every 20 times of working fine.
3- I tried to override onChange but an exception was thrown because it cannot cast LiveData to LifeCycleOwner
4- TableEntity is just a Room Entity contains fields about a restaurant table
//the constructor
private DataRepository(AppDatabase database
) {
this.mDatabase = database;
executor = new AppExecutors();
mObserableTables = new MediatorLiveData<>();
mObserableTables.addSource(mDatabase.tableDao().loadAllTables(),
tableEntities -> {
if (mDatabase.getDatabaseCreated().getValue() != null)
mObserableTables.postValue(tableEntities);
});
}
private MediatorLiveData<List<TableEntity>> mObserableTables;
public void refereshTables()
{
executor.networkIO().execute(()->
{
try {
if(mObserableTables.getValue()==null || mObserableTables.getValue().isEmpty()) {//<-the problem
List<TableEntity> tableEntities = new WebserviceCall().getTablesOnline();
mDatabase.tableDao().insertOrReplaceAllTables(tableEntities);
}
}catch (IOException e)
{
Log.d(this.getClass().getName(),e.getMessage());
}
});
}
public void refreshTablesForService()
{
executor.diskIO().execute(()->
{
int deletedRows=mDatabase.tableDao().deleteAllTables();
Log.d(this.getClass().getName()
,"deleted rows from tables= "+String.valueOf(deletedRows));
mObserableTables.postValue(null);
refereshTables();
});
}
answering my own question :)
after hours of deep digging, I discovered that nothing to do getValue().
the problem was in .postValue() method which is used UiThread to put the value in LiveData. this for sure requires adding a task to UI which may be delayed and checking the data before it finished this task leads to inconsistency.
even trying to load all data from the table was providing inconsistency information.
trying to removeSource() will do nothing about this problem too.
I managed to handle this issue by using "SELECT COUNT(*) FROM tables" query which gives me accurate information.
btw, adding "Thread.sleep(100)" before calling refereshTables(); fixes the problem but give a very bad user experience.
a fantastic explanation allowed me to figure this out is https://code.tutsplus.com/tutorials/android-architecture-component-livedata--cms-29317

ConcurrentModificationException crashes my app

My Android app has an IntentService where it requests a list of MessageThreads objects from Facebook, parses the JSON response to build an ArrayList of the objects:
ArrayList<MessageThread> mMessageThreads = new ArrayList<MessageThread>();
Then it calls FB again in the same service, get the names for the MessageThread ids and matches them with the MessageThread objects. At this point I have an ArrayList with complete MessageThread objects and I insert them into an SQLite db.
// save to db and broadcast
for (MessageThread message : mMessageThreads) {
((FBClientApplication)getApplication()).getMessagesData().insertOrIgnore(message.toContentValues();
}
where:
public void insertOrIgnore(ContentValues values) {
SQLiteDatabase db = this.dbHelper.getWritableDatabase();
db.insertWithOnConflict(TABLE, null, values, SQLiteDatabase.CONFLICT_IGNORE);
}
Via ACRA reports I see that intermittently the line
for (MessageThread message : mMessageThreads)
throws an ConcurrentModificationException and the app forcloses. I haven't been able to isolate under what conditions. I read about this Exception and as I understand it it happens when we remove items from an ArrayList while iterating over it, but I'm not removing items from the list. Any pointers to help with this problem are greatly appreciated.
It also happens when you add items to an ArrayList while iterating over it, which it looks like you might do in this code.
In general, it's any "structural modification" that occurs to the ArrayList that can cause a CME while iterating.
What you can try to do is when you iterates your Collection instead of using the original you can make a copy right there, so you will have something like:
for (MessageThread message : new List<MessageThread>(mMessageThreads))
That will help you to avoid CuncurrentModificationException.
Now if you really want to get fancy you can protect your code using synchronized blocks such as:
synchronized(mMessageThreads){
for (MessageThread message : new List<MessageThread>(mMessageThreads)){
...
}
With this last pice of code you will restrict the access to mMessageThreads, if somebody it's using it it will get locked, so if somebody else wants to use it needs to wait until the first one is done.

Categories

Resources