Android: Parse.com concurrency issue with findInBackground() - java

I am using Parse.com as a backend for my app. The local database from Parse seems to be very easy to use, so I decided to use it.
I want to create a database with Name and PhoneNumber. That is easy, just make a new ParseObject and pinInBackground(). But it is more complicated when I want to remove duplicate numbers. First I need to search if the number already exists in the database and then add the new number if it doesn't exists.
The method to do this is:
public void putPerson(final String name, final String phoneNumber, final boolean isFav) {
// Verify if there is any person with the same phone number
ParseQuery<ParseObject> query = ParseQuery.getQuery(ParseClass.PERSON_CLASS);
query.whereEqualTo(ParseKey.PERSON_PHONE_NUMBER_KEY, phoneNumber);
query.fromLocalDatastore();
query.findInBackground(new FindCallback<ParseObject>() {
public void done(List<ParseObject> personList,
ParseException e) {
if (e == null) {
if (personList.isEmpty()) {
// If there is not any person with the same phone number add person
ParseObject person = new ParseObject(ParseClass.PERSON_CLASS);
person.put(ParseKey.PERSON_NAME_KEY, name);
person.put(ParseKey.PERSON_PHONE_NUMBER_KEY, phoneNumber);
person.put(ParseKey.PERSON_FAVORITE_KEY, isFav);
person.pinInBackground();
} else {
Log.d(TAG, "Warning: " + "Person with the number " + phoneNumber + " already exists.");
}
} else {
Log.d(TAG, "Error: " + e.getMessage());
}
}
}
);
}
Lets say I want to add 3 persons in the database:
ParseLocalDataStore.getInstance().putPerson("Jack", "0741234567", false);
ParseLocalDataStore.getInstance().putPerson("John", "0747654321", false);
ParseLocalDataStore.getInstance().putPerson("Jack", "0741234567", false);
ParseLocalDataStore.getInstance().getPerson(); // Get all persons from database
Notice that first and third person have the same number so the third souldn't be added to database, but...
The logcat after this is:
12-26 15:37:55.424 16408-16408/D/MGParseLocalDataStore: Person:0741234567 was added.
12-26 15:37:55.424 16408-16408/D/MGParseLocalDataStore: Person:0747654321 was added.
12-26 15:37:55.484 16408-16408/D/MGParseLocalDataStore: Person:0741234567 was added.
12-26 15:37:55.494 16408-16408/D/MGParseLocalDataStore: Person database is empty
The last line from logcat is from the method that shows me all persons from database:
public void getPerson() {
ParseQuery<ParseObject> query = ParseQuery.getQuery(ParseClass.PERSON_CLASS);
query.fromLocalDatastore();
query.findInBackground(new FindCallback<ParseObject>() {
public void done(List<ParseObject> personList,
ParseException e) {
if (e == null) {
if (personList.isEmpty()) {
Log.d(TAG, "Person database is empty");
} else {
for (ParseObject p : personList) {
Log.d(TAG, p.getString(ParseKey.PERSON_PHONE_NUMBER_KEY));
}
}
} else {
Log.d(TAG, "Error: " + e.getMessage());
}
}
});
}
So there are 2 problems:
The third number is added even if I checked if already exists.
The method that shows me all persons tell me I have nothing in my database even if in logcat I can see it added 3 persons.
I think the problem is findInBackground() method that does all the job in another thread.
Is there any solution to this problem?

Both of your problems are a result of asynchronous work. If you call the putPerson method twice, they will both run near-simultaneously in separate background threads and both find-queries will most likely return almost at the same time, and definitely before the first call has saved the new person.
In your example, the getPerson call will return before the background threads have been able to save your three people as well.
Your problem is not really related to Parse or localDataStore, but is a typical concurrency issue. You need to rethink how you handle concurrency in your app.
As long as this is only a local issue, you can impose synchronous structure with i.e. the Bolts Framework (which is already a part of your app since you're using Parse). But if calls to addPerson is done in multiple places, you will always face this problem and you'd have to find other solutions or workarounds to handle concurrency.
Concurrency is a big topic which you should spend some time studying.

Related

How to check which document is deleted on Firestore in Java - Android Studio

I have a collection on Firestore called "Users" and within this collection I have various documents which are phone numbers. Let's say that I have a code on android that can delete numbers with a button. Now how can I see through another code, constantly, which document (number) is being deleted?
To detect when a document is deleted on another client, you can attach a realtime listener to a query for only that document and then check the changes between snapshots until the document gets deleted.
To query for a single document, you can set an equality condition on FieldPath.documentId().
So combined that'd be something like:
db.collection("users")
.whereEqualTo(FieldPath.documentId(), "idOfDocumentToListenTo")
.addSnapshotListener(new EventListener<QuerySnapshot>() {
#Override
public void onEvent(#Nullable QuerySnapshot snapshots,
#Nullable FirebaseFirestoreException e) {
if (e != null) {
Log.w(TAG, "listen:error", e);
return;
}
for (DocumentChange dc : snapshots.getDocumentChanges()) {
switch (dc.getType()) {
case ADDED:
Log.d(TAG, "New user: " + dc.getDocument().getData());
break;
case MODIFIED:
Log.d(TAG, "Modified user: " + dc.getDocument().getData());
break;
case REMOVED:
Log.d(TAG, "Removed user: " + dc.getDocument().getData());
// 👆 this is what you're looking for
break;
}
}
}
});
Note that this approach can only detect the deletion when it actively happens. If the deletion happened before the call to addSnapshotListener, there is no way for a client to know about it anymore and you'd likely have to look at something like Cloud Functions or change the data model to not actually delete the document but instead set a field to mark it as deleted.

Finding which item(s) in a list is not present in the Realm

I am using the latest Java Realm local database backing a mobile app.
I have a Profile class and a Contact class in the app. Each Profile object can be associated with one or more Contact objects.
Upon startup, I want to reconcile against a similar Profile/Contact list on my website to insure that the mobile app is using the latest/greatest definition of the Profile/Contact relationship.
After validating the Profile during login, I then query the website to get the list of Contact email addresses associated with the Profile. I now need to do the following:
For each Contact email address in the list from the website, make sure that the local Realm Contact object shows as "connected" to the Profile object.
For each Contact object NOT in the list from the website, make sure that the local Realm Contact object shows as "not connected" to the Profile object.
So what I'm doing is after login, executing an AsyncTask that:
try {
realm.beginTransaction();
RealmResults<Contact> contactResults;
contactResults = realm.where(Contact.class).findAll();
if ( contactResults.size() == 0 ) {
// Nothing to do
return false;
}
// First set everything as not connected, then set only those passed in as connected
contactResults.setBoolean(IS_CONNECTED, false);
// Now update just the email addresses passed in as connected
contactResults = realm.where(Contact.class).in(EMAIL_ADDRESS, emailList.toArray(new String[0])).findAll();
if ( contactResults.size() > 0) {
contactResults.setBoolean(IS_CONNECTED, true);
}
//TODO - what if an item passed in is not yet in Contact list?
success = true;
} catch (Exception e) {
Timber.d("Exception resetting contacts status: %s", e.getMessage());
} finally {
if (success) {
realm.commitTransaction();
} else {
realm.cancelTransaction();
}
}
How I've done the first two pieces of work seem ok to me - as this app won't be scaling to thousands of "friends", resetting the contact to FALSE for everyone then to TRUE for the current set retrieved from the website is doable.
However, the third piece has me stumped - is there a single call I can make to identify which, if any, of the email addresses in the passed array aren't actually in my Contact object RealmResults and therefore need to be added as new objects?
Seems inefficient to loop through that list and check each one, one at a time, particularly since I've already performed two queries.
The only solution I've come up with so far is this:
try {
realm.beginTransaction();
fullContactResults = realm.where(Contact.class).findAll();
if ( fullContactResults.size() > 0 ) {
// First set everything as not connected, then set only those passed in as connected
fullContactResults.setBoolean(IS_CONNECTED, false);
}
// Now for each item in the list we were passed in find it and update it, or add it.
for ( String data : contactData ) {
Contact c = null;
String[] contactStruct = data.split(BODY_COMPONENTS_SEPARATOR);
RealmResults<Contact> partialContactResults = realm.where(Contact.class)
.equalTo(EMAIL_ADDRESS, contactStruct[CONNECTION_EMAIL_ELE])
.findAll();
if (partialContactResults.size() > 0 ) {
c = partialContactResults.first();
} else {
// Need to add the contact
c = realm.createObject(Contact.class, UUID.randomUUID().toString());
}
c.setConnected(true);
c.setDisplayName(contactStruct[CONNECTION_FIRSTNAME_ELE] + " " + contactStruct[CONNECTION_LASTNAME_ELE]);
c.setEmailAddress(contactStruct[CONNECTION_EMAIL_ELE]);
}
success = true;
} catch (Exception e) {
Timber.d("Exception resetting contacts status: %s", e.getMessage());
} finally {
if (success) {
realm.commitTransaction();
} else {
realm.cancelTransaction();
}
}
Which does a loop and either updates or inserts.
Any better way?

FirestoreException: Backend ended Listen Stream

I'm trying to use Firestore in order to set up realtime listeners for a collection. Whenever a document is added, modified, or deleted in a collection, I want the listener to be called. My code is currently working for one collection, but when I try the same code on a larger collection, it fails with the error:
Listen failed: com.google.cloud.firestore.FirestoreException: Backend ended Listen stream: The datastore operation timed out, or the data was temporarily unavailable.
Here's my actual listener code:
/**
* Sets up a listener at the given collection reference. When changes are made in this collection, it writes a flat
* text file for import into backend.
* #param collectionReference The Collection Reference that we want to listen to for changes.
*/
public static void listenToCollection(CollectionReference collectionReference) {
AtomicBoolean initialUpdate = new AtomicBoolean(true);
System.out.println("Initializing listener for: " + collectionReference.getId());
collectionReference.addSnapshotListener(new EventListener<QuerySnapshot>() {
#Override
public void onEvent(#Nullable QuerySnapshot queryDocumentSnapshots, #Nullable FirestoreException e) {
// Error Handling
if (e != null) {
System.err.println("Listen failed: " + e);
return;
}
// If this is the first time this function is called, it's simply reading everything in the collection
// We don't care about the initial value, only the updates, so we simply ignore the first call
if (initialUpdate.get()) {
initialUpdate.set(false);
System.out.println("Initial update complete...\nListener active for " + collectionReference.getId() + "...");
return;
}
// A document has changed, propagate this back to backend by writing text file.
for (DocumentChange dc : queryDocumentSnapshots.getDocumentChanges()) {
String docId = dc.getDocument().getId();
Map<String, Object> docData = dc.getDocument().getData();
String folderPath = createFolderPath(collectionReference, docId, docData);
switch (dc.getType()) {
case ADDED:
System.out.println("Document Created: " + docId);
writeMapToFile(docData, folderPath, "CREATE");
break;
case MODIFIED:
System.out.println("Document Updated: " + docId);
writeMapToFile(docData, folderPath, "UPDATE");
break;
case REMOVED:
System.out.println("Document Deleted: " + docId);
writeMapToFile(docData, folderPath, "DELETE");
break;
default:
break;
}
}
}
});
}
It seems to me that the collection is too large, and the initial download of the collection is timing out. Is there some sort of work around I can use in order to get updates to this collection in real time?
I reached out to the Firebase team, and they're currently getting back to me on the issue. In the meantime, I was able to reduce the size of my listener by querying the collection based on a Last Updated timestamp attribute. I only looked at documents that were recently updated, and had my app change this attribute whenever a change was made.

Java have Alexa Custom Skill ask for missing slots

I am currently building a custom skill for Alexa in Java.
I want Alexa to set an appointment using an existing Exchange Server.
For the appointment I want Alexa to check wether a name, a date and a time are given by the user. I do so using if-statements like:
if(date.getValue() == null) {
return askResponse("Please give a date in order to create an appointment")
What happens is Alexa asks for the missing slot but when I answer the skill just quits. I don't know how to have Alexa recognize my response.
Code is as follows:
public SpeechletResponse getTerminResponse(Slot name, Slot date, Slot time, Session session, IntentRequest request) throws Exception {
if(time.getValue() == null) {
return askResponse("Please insert time");
} else if (date.getValue() == null) {
return askResponse("Please insert date");
} else if (name.getValue() == null) {
return askResponse("Please insert name");
} else {
try {
String[] datumArray = date.getValue().split("-");
String[] zeitArray = time.getValue().split(":");
Date startDate = new Date((Integer.parseInt(datumArray[0])-1900), (Integer.parseInt(datumArray[1])-1), (Integer.parseInt(datumArray[2])), (Integer.parseInt(zeitArray[0])), (Integer.parseInt(zeitArray[1])), 0);
Date endDate = new Date((Integer.parseInt(datumArray[0])-1900), (Integer.parseInt(datumArray[1])-1), (Integer.parseInt(datumArray[2])), (Integer.parseInt(zeitArray[0]))+1, (Integer.parseInt(zeitArray[1])), 0);
System.out.println(startDatum.toString());
System.out.println(endDatum.toString());
ExchangeHelper eh = new ExchangeHelper();
eh.createMeeting(name.getValue(), "Test", startDate, endDate);
return getTellSpeechletResponse("Appointment created successfully");
} catch (Exception e) {
System.out.println(e);
return askResponse("Failed to create appointment");
}
}
}
Here is my Interaction Model
Any help would be highly appreciated since I have been researching documentations and examples for days and I just can not get it to work.
Best regards
Can you give the code for getTellSpeechletResponse?
According to the picture you attached you are using the "new" Dialog model so that Amazon collect all the slots for you intent.
https://developer.amazon.com/public/solutions/alexa/alexa-skills-kit/docs/dialog-interface-reference#directives
Most probably you forgot to send back the DelegateDirective (via speechletResponse.setDirectives(...)) to amazon to tell Alexa to take care for collecting the slot values. But this only can be answered if you send the code. I would also like to see an Dialog Java example by amazon but haven't found yet.
If you are using this dialog model you also don't need the if elses as alexa recognize itself which slots are missing. You have to mark this "Is this slot required to fulfill the intent" with yes in the interaction model. Than you also don't need to create own ask responses but just to give utterances in interaction model for your 4 slots.

Android application exits without any exception

I'm building an application that shows in a WebView some remote data that is cached in SQLite db. The data is being requested by JavaScript function from WebView via JavaScript interface.
When user types into an input element on the page, JavaScript function requests search result by calling Java function, which in turn fires a sql query. Results are then packaged in suitable JSON format and returned.
Fetching data works OK unless you type very quickly. If you type quick enough after few key presses the app quits WITHOUT any exceptions being thrown, it just goes back to home screen.
I have managed to narrow down the cause - commenting out the call to .query method prevents crashing, but renders app useless.
Is there a way to check what caused application to quit, another log or tool that could help?
Java function code:
public Lot[] getLotList(String query, int limitCount) {
...
...
String[] resultColumns = new String[] { LotsSearch._ID };
String queryWhere = LotsSearch.TABLE_NAME + " MATCH ?";
String[] queryArgs = new String[] { query + "*" };
String sortOrder = LotsSearch.COLUMN_NAME_NUMBER + " ASC, " + LotsSearch.COLUMN_NAME_TITLE + " ASC";
String limit = null;
Cursor cursor = null;
if (limitCount != -1)
limit = "0," + limitCount;
try {
cursor = mDb.query(LotsSearch.TABLE_NAME, resultColumns, queryWhere, queryArgs, null, null, sortOrder, limit);
if (cursor != null && cursor.moveToFirst()) {
result = new Lot[cursor.getCount()];
try {
int idColumnIndex = cursor.getColumnIndexOrThrow(LotsSearch._ID);
int lotId;
Lot lot;
do {
lotId = cursor.getInt(idColumnIndex);
lot = mLots.get(lotId);
if (lot != null)
result[index++] = lot;
} while (cursor.moveToNext());
} catch (IllegalArgumentException e) {
e.printStackTrace();
}
}
} catch (SQLiteException e) {
e.printStackTrace();
} finally {
if (cursor != null)
cursor.close();
}
...
...
return result;
}
UPDATE:
I have discovered that there is another log that could be accessed by issuing
logcat -b events
when the app crashes there is just one entry
I/am_proc_died( 59): [11473,com.example.app]
and when the app exits gracefuly this log shows set of entries:
I/am_finish_activity( 59): [1157978656,22,com.example.app/.MainActivity,app-request]
I/am_pause_activity( 59): [1157978656,com.example.app/.MainActivity]
I/am_on_paused_called(11473): com.example.app.MainActivity
I/am_destroy_activity( 59): [1157978656,22,com.example.app/.MainActivity]
I'd make a change to my auto search function. Namely, only perform the search if the user hasn't pressed a key for about 1/2 a second.
If you are typing fast, then this function is being executed several times right on top of itself, before the results are even able to come back. Meanwhile you are probably have too many cursor resources going at once causing the app to just completely fail.
update. If you consider it, typing 10 keys fairly quickly in a row could potentially mean that you have 10 different queries executing and parsing results... There could certainly be some deadlocking issues with the code that actually calls the getLotList method if it's spun multiple threads to try and update the UI. This can lead to some programs simply giving up the ghost not knowing what to do or even what thread to report the issue on.
Of course, all of that's hard to tell from the small snippet we have.

Categories

Resources