Recover Data Firestore Collection - android

I have a service scheduling screen.
But there is a problem:
I need to check if the date and time the user is trying to schedule is available or reserved.
Structure DB:
Companies
-Company ID (Document)
--name
--phone
---Schedules (Collection)
------Event1
--------Hour: 08:30
--------Date: 01/01/2018
------Event2
--------Hour: 09:00
--------Date: 05/01/2018
------Event3
--------Hour: 10:30
--------Date: 01/002/2018
I access Scheduling data with this code:
String dateExample = "01/01/2018"
String hourExample = "08:30"
FirebaseFirestore mDB = FirebaseFirestore.getInstance();
CollectionReference mDBCompaniesSchedules = (CollectionReference) mDB.collection("Companies").document(mId_Company).collection("Schedules")
.get()
.addOnSuccessListener(new OnSuccessListener<QuerySnapshot>() {
#Override
public void onSuccess(QuerySnapshot documentSnapshots) {
for (Schedules document : documentSnapshots.toObjects(Schedules.class)) {
String dtSchedules = document.getSchedules_date();
String hourSchedules = document.getSchedules_hour();
if ( dtSchedules.equals(dateExample) && hourSchedules.equals(hourExample) ){
//Execute a "Toast" and closes the operation
} else {
//Call up the scheduling function.
startScheduling();
}
}
}
})
Process:
I need to run this code and go through all the documents in that collection. I need to check and analyze whether the date and time of the schedule already exists.
If there is: Show a Toast and block.
If there is no: Executing a specific function for the schedule record ("startScheduling()").
Problem:
When the data exists (it will only be once) then it will work.
When there is no data, it falls into the ELSE loop. And it is executing several times the same function "startScheduling();".
I need some way to go through this collection and when I do not find any results, the function "startScheduling (), be executed only once.

This how a for loop works. It will continue iterate till the last element to see if the condition is true or not. With other words, your if-else statement is triggered for every iteration in the loop. It means that, if the condition is true it will go with the if part, if the condition is false it will go with else part, for each and every element.
There are two ways in which you can solve this. One would be to break the loop once the condition was fulfilled. But this means that will iterate till it gets that element. Second, would be to change the logic of your code. Use first the if statement and second iterate.
Edit: The best option in this case would be to query your database using whereEqualTo() method.
Query query = db
.collection("Companies")
.document(mId_Company)
.collection("Schedules")
.whereEqualTo("dtSchedules", dateExample)
.whereEqualTo("hourSchedules", hourExample);
In which dateExample and hourExample are the actual values with which you want to compare.
To count the number of documents in a Collection, please use the following code:
public void onSuccess(QuerySnapshot documentSnapshots) {
if(documentSnapshots.size() == 0) {
startScheduling();
}
for (Schedules document : documentSnapshots.toObjects(Schedules.class)) {
String dtSchedules = document.getSchedules_date();
String hourSchedules = document.getSchedules_hour();
}
}

Related

Why is Parse Server saveEventually with callback taking so long?

I'm using Parse Server for my Android app and everything is working fine, but every time I call saveEventually on a new or old ParseObject, it is taking a really long time. Sometimes it's more than 1 minute for 1 item to return the callback.
Anyone had this problem?
Example:
orderObject.p.apply {
put(ORDER_STATE, ORDER_STATE_FINISHED)
put(ORDER_NEXT_DATE, orderEndDate)
}
createLog("FinishOrderSeq", "OrderActivity - saveOrder - before saveEvent")
orderObject.p.saveEventuallyEx(isOnline(this)){ e ->
createLog("FinishOrderSeq", "OrderActivity - saveOrder - after saveEvent")
if (e == null){
createToast(getString(R.string.order_dialog_success), this)
createOrderCopy(orderObject, dialog)
} else {
createToast(getString(R.string.order_dialog_err), this)
changeButtonState(posBtn, true)
changeButtonState(negBtn, true)
}
}
fun ParseObject.saveEventuallyEx(isOnline: Boolean, callback: (ParseException?) -> Unit){
if (isOnline){
saveEventually{ err ->
callback(err)
}
} else {
saveEventually()
callback(null)
}
}
Also logs as I replaced it with saveInBackground with callback(still 30 seconds):
2020-05-28 14:53:49.805 18673-18673/? I/FinishOrderSeq: OrderActivity - saveOrder - before saveEvent
2020-05-28 14:54:15.694 18673-18673/? I/FinishOrderSeq: OrderActivity - saveOrder - after saveEvent
UPDATE:
So I figured out from parse dashboard, that ParseObject is saved as record in table immediatelly, but callback from saveEventually is sent after 30sec - 2 minutes.
UPDATE 2:
I also tried to use saveInBackground() if user is online (with callback). This also took 30seconds to 2 minutes for callback to return. Object was saved to parse database with all data after 100ms (checked from Parse Dashboard).
Then I thought something is wrong with ParseSDK threads, so I used save() inside Coroutine. Same problem occured here, save() took up to 2 minutes to perform.
Code with coroutine:
fun ParseObject.saveAsync(context: CoroutineContext, scope: CoroutineScope, isOnline: Boolean, callback: (ParseException?) -> Unit){
if (isOnline){
scope.launch {
var ex: ParseException? = null
try {
save()
} catch (e: ParseException){
ex = e
}
withContext(context){
callback(ex)
}
}
}
}
There is some serious problem with callbacks in ParseSDK for Android and I don't know what can cause this. No exception no error on server side.
UPDATE 3:
After deeper investigation, I found which function is taking long time to proceed.
ParseObject.State result = saveTask.getResult();
Approximately 30 seconds - 2 minutes to get into next line of code.
This is lowest level of function I can get inside SDK.
Inside function save() or saveInBackground() there is this inner function in Java:
Task<Void> saveAsync(final String sessionToken, final Task<Void> toAwait) {
if (!isDirty()) {
return Task.forResult(null);
}
final ParseOperationSet operations;
synchronized (mutex) {
updateBeforeSave();
validateSave();
operations = startSave();
}
Task<Void> task;
synchronized (mutex) {
// Recursively save children
/*
* TODO(klimt): Why is this estimatedData and not... I mean, what if a child is
* removed after save is called, but before the unresolved user gets resolved? It
* won't get saved.
*/
task = deepSaveAsync(estimatedData, sessionToken);
}
return task.onSuccessTask(
TaskQueue.<Void>waitFor(toAwait)
).onSuccessTask(new Continuation<Void, Task<ParseObject.State>>() {
#Override
public Task<ParseObject.State> then(Task<Void> task) {
final Map<String, ParseObject> fetchedObjects = collectFetchedObjects();
ParseDecoder decoder = new KnownParseObjectDecoder(fetchedObjects);
return getObjectController().saveAsync(getState(), operations, sessionToken, decoder);
}
}).continueWithTask(new Continuation<ParseObject.State, Task<Void>>() {
#Override
public Task<Void> then(final Task<ParseObject.State> saveTask) {
ParseObject.State result = saveTask.getResult(); <--- THIS IS TAKING LONG TIME
return handleSaveResultAsync(result, operations).continueWithTask(new Continuation<Void, Task<Void>>() {
#Override
public Task<Void> then(Task<Void> task) {
if (task.isFaulted() || task.isCancelled()) {
return task;
}
// We still want to propagate saveTask errors
return saveTask.makeVoid();
}
});
}
});
}
From the docs:
Most save functions execute immediately, and inform your app when the save is complete. If you don’t need to know when the save has finished, you can use saveEventually instead.
It can take a long time because with saveEventually you are basically saying "save it soon". If you want to "save it as soon a possible" then use saveInBackground as described in the docs.
Further it says:
All calls to saveEventually (and deleteEventually) are executed in the order they are called, so it is safe to call saveEventually on an object multiple times. If you have the local datastore enabled, then any object you saveEventually will be pinned as long as that save is in progress. That makes it easy to retrieve your local changes while waiting for the network to be available.
Which means that you can save and modify the object locally multiple times and the latest version will be stored in the database as soon as the network connection is reestablished.

Documents have not changed but considered as added in Firestore Android Studio [duplicate]

I've searched everywhere with no luck. I want to query Firestore to get all users WHERE type is admin. Something like:
SELECT * FROM users WHERE type=admin
but only when the property total is changing. If I'm using:
users.whereEqualTo("type", "admin").addSnapshotListener(new EventListener<QuerySnapshot>() {
#Override
public void onEvent(#Nullable QuerySnapshot snapshots, #Nullable FirebaseFirestoreException e) {
for (DocumentChange dc : snapshots.getDocumentChanges()) {
switch (dc.getType()) {
case ADDED:
//Not trigger
break;
case MODIFIED:
//Trigger
break;
case REMOVED:
//
break;
}
}
}
});
The case ADDED is triggered first time when I query and when the total is changed case MODIFIED is triggered again (this is what is want). I want only changes and not the all initial data, I don't need it. How to get it?
Please help me, is the last part of my project. How to skip is case ADDED?
When you are listening for changes in Cloud Firestore for realtime changes, using Firestore Query's addSnapshotListener() method, it:
Starts listening to this query.
Which basically means that first time you attach the listener, you get all documents that correspond to that particular query. Furthermore, everytime a property within a document changes, you are notified according to that change. Obviously, this is happening only if the listener remains active and is not removed.
Unfortunately, Firestore listeners don't work that way, so you cannot skip that "case ADDED". What you can do instead, is to add add under each user object a Date property (this is how you can add it) and query your database on client, according to this new property, for all documents that have changed since a previous time.
According to Nick Cardoso's comment, for future visitors that might ask why this behaviour happens, is because the reason he mentioned in his comment. I also recommend see Doug Stevenson's answer from this post, for a better understanding.
There is an option to check if the querySnapshot is from a cache, changes return false
if(querySnapshot.getMetadata().isFromCache()) return
Here is a solution working for me:
use
AtomicBoolean isFirstListener = new AtomicBoolean(true);
and then on event method
if (isFirstListener.get()) {
isFirstListener.set(false);
//TODO Handle the entire list.
return;
}
Here is a sample code from my project:
final AtomicBoolean isFirstListener = new AtomicBoolean(true);
mDb.collection("conversation_log").document(room_id).collection("messages").orderBy("sent_at")
.addSnapshotListener(new EventListener<QuerySnapshot>() {
#Override
public void onEvent(#Nullable QuerySnapshot value2, #Nullable FirebaseFirestoreException e) {
if (isFirstListener.get()) {
isFirstListener.set(false);
//TODO Handle the entire list.
return;
}
}
});
reference: answer
Found a work around for this given use case, it is possible to skip the initial data and get only updates. The workaround is including a server timestamp in your structure and build your query to fetch only the data that has timestamp greater than the current time.
val ref = db.collection("Messages").document("Client_ID")
.collection("Private")
.orderBy("timestamp")
.whereGreaterThan("timestamp",Calendar.getInstance().time)
//Add snapshot listener
ref.addSnapshotListener { snapshot , e ->
if (snapshot != null) {
Log.d("TAG", "Current data: ${snapshot.documents.size}")
for(document in snapshot.documents){
Log.e("Document Data",document.data.toString())
}
}
}
So, the query won't return any data in the initial build, but will listen to the document changes. As soon as timestamp of a document changes, you'll be notified about that change. Then you can check if the data exists in your list (if you're looking for modifications, or if it's a new document added)
Just update timestamp of the document when your write any changes. As shown below :
val message = hashMapOf<String,Any>(
"timestamp" to FieldValue.serverTimestamp(),
"id" to userId,
"data" to data,
)
db.collection("Messages").document("Client_ID")
.collection("Private")
.document().set(message)

How to ignore or disable the first query snapshot of addSnapshotListener in firestore for android [duplicate]

I've searched everywhere with no luck. I want to query Firestore to get all users WHERE type is admin. Something like:
SELECT * FROM users WHERE type=admin
but only when the property total is changing. If I'm using:
users.whereEqualTo("type", "admin").addSnapshotListener(new EventListener<QuerySnapshot>() {
#Override
public void onEvent(#Nullable QuerySnapshot snapshots, #Nullable FirebaseFirestoreException e) {
for (DocumentChange dc : snapshots.getDocumentChanges()) {
switch (dc.getType()) {
case ADDED:
//Not trigger
break;
case MODIFIED:
//Trigger
break;
case REMOVED:
//
break;
}
}
}
});
The case ADDED is triggered first time when I query and when the total is changed case MODIFIED is triggered again (this is what is want). I want only changes and not the all initial data, I don't need it. How to get it?
Please help me, is the last part of my project. How to skip is case ADDED?
When you are listening for changes in Cloud Firestore for realtime changes, using Firestore Query's addSnapshotListener() method, it:
Starts listening to this query.
Which basically means that first time you attach the listener, you get all documents that correspond to that particular query. Furthermore, everytime a property within a document changes, you are notified according to that change. Obviously, this is happening only if the listener remains active and is not removed.
Unfortunately, Firestore listeners don't work that way, so you cannot skip that "case ADDED". What you can do instead, is to add add under each user object a Date property (this is how you can add it) and query your database on client, according to this new property, for all documents that have changed since a previous time.
According to Nick Cardoso's comment, for future visitors that might ask why this behaviour happens, is because the reason he mentioned in his comment. I also recommend see Doug Stevenson's answer from this post, for a better understanding.
There is an option to check if the querySnapshot is from a cache, changes return false
if(querySnapshot.getMetadata().isFromCache()) return
Here is a solution working for me:
use
AtomicBoolean isFirstListener = new AtomicBoolean(true);
and then on event method
if (isFirstListener.get()) {
isFirstListener.set(false);
//TODO Handle the entire list.
return;
}
Here is a sample code from my project:
final AtomicBoolean isFirstListener = new AtomicBoolean(true);
mDb.collection("conversation_log").document(room_id).collection("messages").orderBy("sent_at")
.addSnapshotListener(new EventListener<QuerySnapshot>() {
#Override
public void onEvent(#Nullable QuerySnapshot value2, #Nullable FirebaseFirestoreException e) {
if (isFirstListener.get()) {
isFirstListener.set(false);
//TODO Handle the entire list.
return;
}
}
});
reference: answer
Found a work around for this given use case, it is possible to skip the initial data and get only updates. The workaround is including a server timestamp in your structure and build your query to fetch only the data that has timestamp greater than the current time.
val ref = db.collection("Messages").document("Client_ID")
.collection("Private")
.orderBy("timestamp")
.whereGreaterThan("timestamp",Calendar.getInstance().time)
//Add snapshot listener
ref.addSnapshotListener { snapshot , e ->
if (snapshot != null) {
Log.d("TAG", "Current data: ${snapshot.documents.size}")
for(document in snapshot.documents){
Log.e("Document Data",document.data.toString())
}
}
}
So, the query won't return any data in the initial build, but will listen to the document changes. As soon as timestamp of a document changes, you'll be notified about that change. Then you can check if the data exists in your list (if you're looking for modifications, or if it's a new document added)
Just update timestamp of the document when your write any changes. As shown below :
val message = hashMapOf<String,Any>(
"timestamp" to FieldValue.serverTimestamp(),
"id" to userId,
"data" to data,
)
db.collection("Messages").document("Client_ID")
.collection("Private")
.document().set(message)

How to stop execution of piece of code in android until data is retrieved from Firebase?

I am trying to perform some task based on the data retrieved from Firebase.
for (inti=0;i<dateList.size();i++)
{
attendanceDateRef= attendanceRef.child(dateList.get(i));
attendanceClassRef= attendanceDateRef.child(ViewAttendanceSelectClassActivity.selectedClass);
attendanceClassRef.addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshotdataSnapshot) {
for (DataSnapshotdsp : dataSnapshot.getChildren())
{
attendanceList.add(dsp.getValue(String.class));
Log.e("attendanceListValues",dsp.getValue(String.class));
}
}
#Override
public void onCancelled(DatabaseErrordatabaseError) {
}
});
}
intindex=1;
for (inti=1;i<=attendanceList.size();i++)
{
if(i%4==0)
{
fullDateRangeList.add(dateList.get(index));
index++;
}
else
{
fullDateRangeList.add(dateList.get(index));
}
}
Log.e("fullDateRangeList",String.valueOf(fullDateRangeList.size()));
Log.e("attendanceList",String.valueOf(attendanceList.size()));
above code is written on the OnClick event of a Button , when I click on the Button following output is generated on logcat:
fullDateRangeList:0
attendanceList:0
attendanceListValues:Value1
attendanceListValues:Value2
attendanceListValues:Value3
attendanceListValues:Value4
.
.
.
attendanceListValues:ValueN
from the above output it looks like second loop is executing before data is retrieved from Firebase and that is why size of fullDateRange and attendanceList is 0.
is there any way i can prevent second loop from executing until data is stored in attendanceList?
You cannot stop that method or make it wait until you get the all the data from your database. This is the behaviour of an asynchronous method. You need to change the logic of your code a little bit by declaring and using that data only inside the onDataChange() method, otherwise it will be always empty.
Also there is another approach. If you want, you can dive into the asynchronous world and use my answer from this post.

Android - onDataChange() strange behavior

I create an app with Firebase. There is an issue that i can't solve, and didn't find it talked here.
In this method I want to check if some data is already in the server. If not - I want to add it (the code of adding works well. The Firebase database is being changed as I want). so I'm using onDataChange method as following:
public boolean insertNewList(String title)
{
System.out.println("start: ");
final boolean[] result = new boolean[1];
result[0]=false;
final String group = title;
mRootRef = some reference...
mRootRef.addValueEventListener(new ValueEventListener()
{
#Override
public void onDataChange(DataSnapshot dataSnapshot)
{
System.out.println(0);
if (dataSnapshot.child(group).getValue() != null)
{
System.out.println(group + " is already exist");
System.out.println(1);
}
//write the data.
else
{
mRootRef=mRootRef.child(group);
mRootRef.push().setValue("some data");
System.out.println(2);
result[0]=true;
}
}
#Override
public void onCancelled(DatabaseError databaseError)
{
}
});
System.out.println(3);
return result[0];
}
But what realy happens is this output:
begin:
3 (just skip on onDataChange method and return false).
some print after calling the function
0 (goes back to function and enter to onDataChange method)
2 (finally goes where I want it to go)
0 (for some reason enters twice :( )
1
And because of that i receive wrong results in this function.
Can you help please?
Replace
mRootRef.addValueEventListener(new ValueEventListener()
with
mRootRef.addListenerForSingleValueEvent(new ValueEventListener()
When you add the the value to firebase, "addValueEventListener" called again, not like addListenerForSingleValueEvent that shots only once anywhy.
The output that you showed us looks normal to me. Let me try to explain:
begin: // a) this comes from the System.out.println("begin")
3 // b) you DECLARE your value event listener and then you run
// the System.out.print("3") statement
0 // c) you just added a listener so firebase calls you, and
// your listener fires
2 // d) this is the first time your listener fires, so you go
// into the block called "write the data"
0 // e) since you just wrote the data, firebase calls you again
1 // f) this time, since the data has been written, you go into
// the block called "is alraedy exist"
This is normal behaviour for firebase.
In c), firebase always calls you back one time when you declare a listener.
In e), firebase calls you because the data changed.
But in b), you are only declaring your listener, not yet running it, so the statements after this declaration are executed and you see "3" before anything else happens.

Categories

Resources