I have this problem sometimes, when after I request Firebase Firestore Documents, it RETRIEVES ALL THE DATA, shows it correctly in the RecyclerView, but then the Data just disappears after one second.
adapter.getItemCount(); shows, there are Items. I just can´t figure it out...
Here is the retrieval code for the first Cardview:
void getData(){
FirebaseUser user = FirebaseAuth.getInstance().getCurrentUser();
final FirebaseFirestore db = FirebaseFirestore.getInstance();
DocumentReference doc =db.collection("data").document("codes");
doc.get().addOnCompleteListener(new OnCompleteListener<DocumentSnapshot>() {
#Override
public void onComplete(#NonNull Task<DocumentSnapshot> task) {
userWorkList.clear();
if (task.isSuccessful()) {
DocumentSnapshot document = task.getResult();
if (document.exists()) {
final ArrayList codes = (ArrayList) document.get("code");
System.out.println("Codes: "+codes);
for(int i=0;i<codes.size();i++) {
DocumentReference doc =db.collection("codes").document(codes.get(i).toString());
final int finalI = i;
doc.get().addOnCompleteListener(new OnCompleteListener<DocumentSnapshot>() {
#Override
public void onComplete(#NonNull Task<DocumentSnapshot> task) {
if (task.isSuccessful()) {
DocumentSnapshot document = task.getResult();
if (document.exists()) {
if(document.get("code")!=null) {
HashMap codeMap = (HashMap) document.get("code");
if (codeMap.toString().toLowerCase().contains(user.getDisplayName().toLowerCase())){
userWorkList.add(new User_Work_View_Row(codes.get(finalI).toString(), user.getEmail(), "", finalI));
System.out.println(codes.get(finalI) + " FOUND");
//WorkAdapter.notifyDataSetChanged();
}
if(finalI==codes.size()-1){
Handler handler = new Handler();
Runnable runnable = new Runnable() {
#Override
public void run() {
System.out.println("Execute Handler");
if(WorkAdapter.getItemCount()==0&&userWorkList.size()==0){
System.out.println("NO ITEMS");
getData();
//handler.postDelayed(this, 2500);
}
}
};
handler.postDelayed(runnable, 1000);
swipeLayout.setRefreshing(false);
WorkAdapter.notifyDataSetChanged();
}
}
}
} else {
Log.d(TAG, "get failed with ", task.getException());
}
}
});
}
//WorkAdapter.notifyDataSetChanged();
//userWorkProgress.setVisibility(View.GONE);
} else {
Log.d(TAG, "get failed with ", task.getException());
}
}
}
});
}
The adapter Init:
RecyclerView list = root.findViewById(R.id.currentUserWork);
LinearLayoutManager LayoutManager = new LinearLayoutManager(getActivity());
LayoutManager.setReverseLayout(false);
LayoutManager.setStackFromEnd(false);
list.setLayoutManager(LayoutManager);
WorkAdapter.setHasStableIds(true);
WorkAdapter.setGlobalContext(getContext());
list.setAdapter(WorkAdapter);
The Adapter Code really isn´t important because it only shows the Title of the next RecyclerView.
I would love to share a video of it happening, but exactly when I want it to disappear, it shows correctly.
Has someone experienced this sort of thing before or is it a bug?
Oh ok, I THINK I figured it out, I had 0dp for my RecyclerView instead of wrap_content.
But I need to run it a few times before I am certain..
possible trouble in code:
you makes loop
for (int i=0;i<codes.size();i++) {
and in the loop you creates many OnCompleteListeners
the problem here is, that they could be finished in different order.
so your finalI could come before all results are done
UPDATE
To solve this problem
create global variable int requestsLeft
after if (document.exists()) { set requestsLeft = codes.size()
after public void onComplete(#NonNull Task<DocumentSnapshot> task) { make requestsLeft--
instead of if(finalI==codes.size()-1){ you can now check if (requestsLeft == 0) {
so you are sure, that all requests are done
Related
I want to be able to access the images collection and get all values of the document when a user is logged in.When logged in as a user , i want to get all image url's and other fields that are associated with my userID.That is my database and this is my code that crashes my app :
fStore.collection("images").document(FirebaseAuth.getInstance().getCurrentUser().getUid()).get().addOnCompleteListener(new OnCompleteListener<DocumentSnapshot>() {
#Override
public void onComplete(#NonNull Task<DocumentSnapshot> task) {
if(task.isSuccessful()){
for(int i = 0; i < Objects.requireNonNull(task.getResult()).getData().size(); i++){
itemList.add(new Item(task.getResult().getData().get("image"+i).toString(), task.getResult().getData().get("name"+i).toString(), task.getResult().getData().get("price"+i).toString(), task.getResult().getData().get("description"+i).toString()));
}
shoeAdapter = new ShoeAdapter(MainActivity.this, itemList);
recyclerView.setAdapter(shoeAdapter);
shoeAdapter.notifyDataSetChanged();
}
}
});
This was the first solution but then i realized that i didnt cater for the currently logged in user so i was able to see the same information in a recyclerview even when i switch accounts.
for (int i = 0; i < Objects.requireNonNull(task.getResult()).size(); i++) {
Item item = new Item();
item.setItemName(task.getResult().getDocuments().get(i).getString("name"));
item.setItemPrice(task.getResult().getDocuments().get(i).getString("price"));
item.setItemDescription(task.getResult().getDocuments().get(i).getString("description"));
item.setItemImage(task.getResult().getDocuments().get(i).getString("image"));
itemList.add(item);
}
shoeAdapter = new ShoeAdapter(this, itemList);
recyclerView.setAdapter(shoeAdapter);
shoeAdapter.notifyDataSetChanged();
});
Try below query to get multiple documents inserted by the currently logged in user-
db.collection("images")
.whereEqualTo("user_id", FirebaseAuth.getInstance().getCurrentUser().getUid())
.get()
.addOnCompleteListener(new OnCompleteListener<QuerySnapshot>() {
#Override
public void onComplete(#NonNull Task<QuerySnapshot> task) {
if (task.isSuccessful()) {
for (QueryDocumentSnapshot document : task.getResult()) {
Item item = new Item();
item.setItemName(document.getData().get("name"));
item.setItemPrice(document.getData().get("price"));
item.setItemDescription(document.getData().get("description"));
item.setItemImage(document.getData().get("image"));
itemList.add(item);
}
shoeAdapter = new ShoeAdapter(MainActivity.this, itemList);
recyclerView.setAdapter(shoeAdapter);
shoeAdapter.notifyDataSetChanged();
} else {
Log.d(TAG, "Error getting documents: ", task.getException());
}
}
});
Check document for more such queries - https://firebase.google.com/docs/firestore/query-data/queries
i created two methods to get data from firestore. The first method is iterating over a list of attributes which are selected by the user. The other method is querying data depending on the selections of the user. After iterating over each item the new method needs to start. The problem is that my list is always empty, even though there is data available. Here are both methods:
private void getStores(ArrayList<String> checkedItems) {
newSeller = new ArrayList<>();
sellers = new ArrayList<>();
a = 0;
for (int i =0; i<checkedItems.size(); i++){
String checkedItem = checkedItems.get(i);
int finalA = i;
getStore(checkedItem);
}
}
private void getStore(String checkedItem) {
db.collection("Seller")
.whereEqualTo("typ", checkedItem)
.get()
.addOnCompleteListener(new OnCompleteListener<QuerySnapshot>() {
#Override
public void onComplete(#NonNull Task<QuerySnapshot> task) {
if (task.isSuccessful()) {
for (QueryDocumentSnapshot document : task.getResult()) {
SellerObject so = document.toObject(SellerObject.class);
so.setId(document.getId());
sellers.add(so);
Log.i(TAG, "onComplete: toa"+sellers.size());
}
} else {
Log.d(TAG, "Error getting documents: ", task.getException());
}
}
});
}
You are declaring the "sellers" array inside the first function but then trying to add to it from the second function. Move sellers out of the function so it can be reached.
I have created a database in firestore and loaded the test data successfully. But later I noticed that some data is missing in database. I found that when I load the same records with a different value for some fields the old record is replaced with the new record. I feel like this is the issue in the database design. I need to collect and save all the records even if it is a duplicate records at entire records level.
Could you please let me know how to do this ?
private void exportToFireStore() {
// Access a Cloud Firestore instance from your Activity
final FirebaseFirestore db = FirebaseFirestore.getInstance();
/* ----------------_-------Collection delete is not supported-----------------------
----------Hence get all the document (coins) for individual Docs delete----------
*/
//------------------------------- Getting document (coins) Ends ---------------------------------------------
final List<String> coinsFromFirestore = new ArrayList<>();
db.collection("cryptos").document(userEmailID).collection("coin")
.whereEqualTo("createdBy", userEmailID)
.get()
.addOnCompleteListener(new OnCompleteListener<QuerySnapshot>() {
#Override
public void onComplete(#NonNull Task<QuerySnapshot> task) {
if (task.isSuccessful()) {
for (QueryDocumentSnapshot document : task.getResult()) {
System.out.println("Testing 1 Batch Read done" + document.getData());
coinsFromFirestore.add(document.getData().get("coinname").toString());
}
//------------------------------- Getting document (coins) Ends ---------------------------------------------
if(coinsFromFirestore.size()>0){
for (int i=0;i<coinsFromFirestore.size();i++) {
if ( i<(coinsFromFirestore.size()-1) ) {
db.collection("cryptos").document(userEmailID).collection("coin").document(coinsFromFirestore.get(i))
.delete()
.addOnSuccessListener(new OnSuccessListener<Void>() {
#Override
public void onSuccess(Void aVoid) {
System.out.println("Testing 1 Successfully Deleted the document " );
}
})
.addOnFailureListener(new OnFailureListener() {
#Override
public void onFailure(#NonNull Exception e) {
System.out.println("Testing 1 Error Deleting the document ");
}
});
}else{
db.collection("cryptos").document(userEmailID).collection("coin").document(coinsFromFirestore.get(i))
.delete()
.addOnSuccessListener(new OnSuccessListener<Void>() {
#Override
public void onSuccess(Void aVoid) {
addTranToFireBaseeNow(db);
}
})
.addOnFailureListener(new OnFailureListener() {
#Override
public void onFailure(#NonNull Exception e) {
}
});
//!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! For last coin Ends !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
}
}
}else{
addTranToFireBaseeNow(db);
}
} else {
Log.d(TAG, "Error getting documents: ", task.getException());
}
}
});
//------------------------------- Getting document (coins) Ends ---------------------------------------------
}
private void addTranToFireBaseeNow(FirebaseFirestore db) {
WriteBatch batch = db.batch();
DocumentReference newCoinRef;
//CollectionReference cryptos = db.collection("cryptos");
List<Tran> tranList = getAllTranForFireStore();
String firebaseUID = FirebaseAuth.getInstance().getCurrentUser().getUid();
for (Tran t : tranList) {
Map<String, Object> tranData = new HashMap<>();
tranData.put("firebaseid", firebaseUID);
tranData.put("createdBy", userEmailID);
tranData.put("coinid", t.getCoinID());
tranData.put("coinname", t.getCoinName());
tranData.put("coinsymbol", t.getCoinSymbol());
tranData.put("date", String.valueOf(t.getDate()));
tranData.put("qty", String.valueOf(t.getQty()));
tranData.put("price", String.valueOf(t.getPrice()));
tranData.put("priceunit", String.valueOf(t.getPriceUnit()));
newCoinRef= db.collection("cryptos").document(userEmailID).collection("coin").document(t.getCoinName());
batch.set(newCoinRef, tranData);
}
batch.commit().addOnCompleteListener(new OnCompleteListener<Void>() {
#Override
public void onComplete(#NonNull Task<Void> task) {
// ...
}
});
}
No index is setup for my DB
Since you are using set without any options, it will overwrite the existing data. But your requirement is to merge your data, so you have to use the merge option as follows:
batch.set(newCoinRef, tranData, SetOptions.merge());
You can read more about options here.
Furthermore there is a good post which lists the differences between set, update and create.
You can use update and change one variable
reference.document("documentname").update("field", variable)
or
reference.document("documentname").set({
field: variable
}, { merge: true });
Or.. if you have to update an entire object, you can use:
reference.document("documentname").set(newObject, { merge: true });
Check this article:
https://saveyourtime.medium.com/firebase-cloud-firestore-add-set-update-delete-get-data-6da566513b1b
I'm writing an app in Android Studio. It connects to Firebase Firestore.
The data is structured like this (I've removed the irrelevant bits, for clarity):
Boxes
- box1
- grid_id
- table_id
-box2
- grid_id
- table_id
Grids
- grid1
- grid2
Tables
- table1
- table2
So, each time I retrieve a box, I also need to retrieve the corresponding Grid and Table.
In my Activity, I have the following:
public class BoxViewActivity {
String box_id;
Box box;
FirebaseFirestore databaseRef;
BoxFragment boxFragment;
GridFragment gridFragment;
TableFragment tableFragment;
FragmentManager fragmentManager;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_edit_box);
fragmentManager = getSupportFragmentManager();
boxFragment = fragmentManager.findFragmentById(R.id.fragment_box);
// Retrieve Box from database and set it up
Intent intent = getIntent();
box_id = intent.getStringExtra("BoxId");
box = new Box();
databaseRef = FirebaseFirestore.getInstance();
boxDocRef = databaseRef.collection("boxes").document(box_id);
boxDocRef.get().addOnCompleteListener(new OnCompleteListener<DocumentSnapshot>() {
#Override
public void onComplete(#NonNull Task<DocumentSnapshot> task) {
if (task.isSuccessful()) {
DocumentSnapshot document = task.getResult();
if (document.exists()) {
// Set up Box
box = document.toObject(Box.Class);
if (box != null) {
// Set up some box views
}
// Get grid/table key
setUpGrid(box.getGridId());
setUpTable(box.getTableId());
}
}
}
});
}
private void setUpGrid(String gridId) {
DocumentReference gridRef = databaseRef.collection("grids").document(gridId);
gridRef.addOnCompleteListener(new OnCompleteListener<DocumentSnapshot>() {
#Override
public void onComplete(#NonNull Task<DocumentSnapshot> task) {
if (task.isSuccessful()) {
DocumentSnapshot document = task.getResult();
if (document.exists()) {
// Retrieve Grid
Grid grid= document.toObject(Grid);
if (grid != null) {
boxFragment.setGrid(grid);
}
}
}
}
});
}
private void setUpTable(String tableId) {
if(tableId == null) return;
DocumentReference tableRef = databaseRef.collection("tables").document(tableId);
tableRef.get().addOnCompleteListener(new OnCompleteListener<DocumentSnapshot>() {
#Override
public void onComplete(#NonNull Task<DocumentSnapshot> task) {
if (task.isSuccessful()) {
DocumentSnapshot document = task.getResult();
if (document.exists()) {
Table table = document.toObject(Table.Class);
if (table != null) {
boxFragment.setTable(table);
}
}
}
});
}
}
The problem I'm having is that the app seems to 'freeze' and then restart when I hit this activity. The screen goes blank and unresponsive, and then the app restarts. Android Studio isn't throwing any errors, and the debug console has nothing. I suspect it's because I'm retrieving the grid and table simultaneously - would that slow it down? If so, what's a better way to do it? Or have I missed something else?
To solve this, I suggest you add a Grid and a Table object within a Box object. In this way you'll be able to a use a single listener instead of three. So your code should look like this:
boxDocRef = databaseRef.collection("boxes").document(box_id);
boxDocRef.get().addOnCompleteListener(new OnCompleteListener<DocumentSnapshot>() {
#Override
public void onComplete(#NonNull Task<DocumentSnapshot> task) {
if (task.isSuccessful()) {
DocumentSnapshot document = task.getResult();
FragmentManager fragmentManager = getSupportFragmentManager();
BoxFragment boxFragment = fragmentManager.findFragmentById(R.id.fragment_box);
if (document.exists()) {
// Set up Box
box = document.toObject(Box.Class);
if (box != null) {
// Set up some box views
// Get grid/table key
Grid grid = box.getGrid();
boxFragment.setGrid(grid);
Table table = box.getTable();
boxFragment.setTable(table);
}
}
}
}
});
And remove the declaration of your FragmentManager and BoxFragment. Both should only be present inside the onComplete() method.
I would like to load RecyclerView from my firestone db. My firestore structure is like this:
users(collection) -> user_id(document) -> books(collection)
books(collection) -> book_id(document)
Added: Database structure images
enter image description here
enter image description here
enter image description here
What I want to do is getting the book_ids of current_user into an ArrayList<String> then with this arraylist and for each loop, I want to load books into another ArrayList<Book>. I am getting the “ArrayList book_ids” of the current user. (I want to show the books which were added by current user)
The problem is with this array list I can not manage to get ArrayList<Book>books. I will send this array list to recycler view adapter.
public void getBookIdsFromDb(){
final ArrayList<String> myBookIds = new ArrayList<String>();
CollectionReference bookIdRef = db.collection(getString(R.string.collection_users)).document(userId)
.collection(getString(R.string.colleciton_books));
bookIdRef.get().addOnCompleteListener(new OnCompleteListener<QuerySnapshot>() {
#Override
public void onComplete(#NonNull com.google.android.gms.tasks.Task<QuerySnapshot> task) {
if (task.isSuccessful()){
for (DocumentSnapshot ds : task.getResult()){
myBookIds.add(ds.getString("book_id"));
myBookIds.size());
}
Log.d(TAG, "onComplete: myBookIds.size" + myBookIds.size());
if (myBookIds.size()>0){
getMyBooksFromDb(myBookIds);
//this works
}
}
}
});
}
public void getMyBooksFromDb(ArrayList<String> bookIds){
final ArrayList<String> myBooksIds =bookIds;
final ArrayList<Book> myBooks = new ArrayList<Book>();
CollectionReference dbRef = db.collection(getString(R.string.colleciton_books));
for (int i =0; i<myBooksIds.size();i++){
Log.d(TAG, "getMyBooksFromDb: myBookIds in for " + myBooksIds.size());
dbRef.document(myBooksIds.get(i)).get().addOnSuccessListener(new OnSuccessListener<DocumentSnapshot>() {
#Override
public void onSuccess(DocumentSnapshot documentSnapshot) {
if (documentSnapshot != null){
Book myBook = documentSnapshot.toObject(Book.class);
myBooks.add(myBook);
//Can not get out of this array from for loop
}
}
});
}
}
//I tried to make a nested query below but no result :(
public void getBookListFromDb(){
final ArrayList<String> myBookIds = new ArrayList<String>();
CollectionReference bookIdRef = db.collection(getString(R.string.collection_users)).document(userId)
.collection(getString(R.string.colleciton_books));
bookIdRef.get().addOnCompleteListener(new OnCompleteListener<QuerySnapshot>() {
#Override
public void onComplete(#NonNull com.google.android.gms.tasks.Task<QuerySnapshot> task) {
final ArrayList<Book> myBooks = new ArrayList<Book>();
if (task.isSuccessful()){
for (DocumentSnapshot ds : task.getResult()){
myBookIds.add(ds.getString("book_id"));
myBookIds.size();
}
Log.d(TAG, "onComplete: myBookIds.size" + myBookIds.size());
if (myBookIds.size()>0){
CollectionReference colRef = db.collection(getString(R.string.colleciton_books));
for (int i=0; i< myBookIds.size(); i++){
colRef.document(myBookIds.get(i)).get().addOnCompleteListener(new OnCompleteListener<DocumentSnapshot>() {
#Override
public void onComplete(#NonNull com.google.android.gms.tasks.Task<DocumentSnapshot> task) {
if (task.isSuccessful()){
DocumentSnapshot ds = task.getResult();
Book myBook = ds.toObject(Book.class);
myBooks.add(myBook);
}
Log.d(TAG, "onComplete: myBooks.size(i) "+ myBooks.size());
}
});
Log.d(TAG, "onComplete: myBooks.size() in for "+ myBooks.size());
}
Log.d(TAG, "onComplete: myBooks.size() "+ myBooks.size());
}
}
}
});
}'
You cannot achieve what you want in this way because both methods, onComplete() and onSuccess() have an asynchronous behaviour. This means that by the time you are calling getMyBooksFromDb() method, you haven't finished getting the data from the database. So to solve this, you need to move all your logic from your getMyBooksFromDb() method inside onComplete() method. This is the flow:
add a complete listener on your bookIdRef
get all the book_id you have in your database by iteration using the foor loop
inside onComplete() method use the book_id to create your CollectionReference for finding the books.
Move the declaration of your ArrayList<Book> inside onSuccess() method.
Do what you want to do with your list or with your Book objects.
So the solution is to use nested queries as explained above. So please use the following code:
CollectionReference bookIdRef = db.collection(getString(R.string.collection_users))
.document(userId)
.collection(getString(R.string.colleciton_books));
bookIdRef.get().addOnCompleteListener(new OnCompleteListener<QuerySnapshot>() {
#Override
public void onComplete(#NonNull com.google.android.gms.tasks.Task<QuerySnapshot> task) {
if (task.isSuccessful()){
for (DocumentSnapshot ds : task.getResult()) {
String bookId = myBookIds.add(ds.getString("book_id"));
dbRef.document(bookId).get().addOnSuccessListener(new OnSuccessListener<DocumentSnapshot>() {
#Override
public void onSuccess(DocumentSnapshot documentSnapshot) {
if (documentSnapshot != null){
Book myBook = documentSnapshot.toObject(Book.class);
Log.d("TAG", myBoog.getBookName());
}
}
});
}
}
}
});
Step -1:
Create a Model Class of Userbook.java and Book.java
Userbook.Java
String book_id;
Book book;
//create getter and setter for that.
As per you code set value:
UserBook userbook = new UserBook();
final ArrayList<UserBook> books = new ArrayList<String>();
public void getBookIdsFromDb(){
CollectionReference bookIdRef = db.collection(getString(R.string.collection_users)).document(userId)
.collection(getString(R.string.colleciton_books));
bookIdRef.get().addOnCompleteListener(new OnCompleteListener<QuerySnapshot>() {
#Override
public void onComplete(#NonNull com.google.android.gms.tasks.Task<QuerySnapshot> task) {
if (task.isSuccessful()){
for (DocumentSnapshot ds : task.getResult()){
userbook.setBook_id(ds.getString("book_id"));
}
Log.d(TAG, "onComplete: myBookIds.size" + myBookIds.size());
if (myBookIds.size()>0){
getMyBooksFromDb(myBookIds);
//this works
}
}
}
});
}
public void getMyBooksFromDb(ArrayList<String> bookIds){
CollectionReference dbRef = db.collection(getString(R.string.colleciton_books));
for (int i =0; i<myBooksIds.size();i++){
Log.d(TAG, "getMyBooksFromDb: myBookIds in for " + myBooksIds.size());
dbRef.document(myBooksIds.get(i)).get().addOnSuccessListener(new OnSuccessListener<DocumentSnapshot>() {
#Override
public void onSuccess(DocumentSnapshot documentSnapshot) {
if (documentSnapshot != null){
Book myBook = documentSnapshot.toObject(Book.class);
userbook.setBook_id(myBook);
//Can not get out of this array from for loop
}
}
});
}
}
//after all this add obj to list
books.add(userbook);
I hope you will get the result. or else ping here.